hyeon.s
개발로그
hyeon.s
전체 방문자
오늘
어제
  • 분류 전체보기 (151)
    • Web 및 인프라 (1)
      • Web (1)
      • Terraform (2)
      • Docker (1)
    • Android (1)
      • 공부 (28)
      • 트러블슈팅 (12)
      • 프로젝트 개발 (10)
      • Compose (2)
      • 우테코 프리코스 (0)
    • Server (6)
      • 공부 (1)
      • Spring (5)
    • 알고리즘 (68)
      • 문제풀이 (C++,Kotlin) (54)
      • 공부 (13)
    • 디자인 (3)
      • UI (3)
    • Language (5)
      • Kotlin (5)
      • JAVA (0)
    • IT 동아리 (8)
      • UMC 3기 (Android) (7)
      • Sopt 32기 (Android) (1)

Github

글쓰기 / 관리자
hELLO · Designed By 정상우.
hyeon.s

개발로그

[Android/공부] JNI 활용 안드로이드 디버깅 / USB 탐지 (JAVA)
Android/공부

[Android/공부] JNI 활용 안드로이드 디버깅 / USB 탐지 (JAVA)

2024. 7. 11. 13:15
728x90

안드로이드 디버깅 탐지란?

안드로이드 모바일 앱에서 LLDB, GDB, IDA 같은 동적 디버깅 도구를 붙였을 때 디버깅이 가능하다면 이는 취약점이 될 수 있다.

앱이 동작할 때 동적 디버깅 도구를 활용한 디버깅 가능 여부에 따라 취약한지 결정된다.

LLDB, GDB, IDA와 같은 동적 디버깅 도구로 디버깅이 가능하다면 공격자는 이를 통해 코드 흐름 파악, 메모리 상태 분석, 실행 흐름을 조작 할 수 있다.

따라서 디버깅 탐지 기능을 통해 동적 도구를 활용한 디버깅을 방지 할 수 있다.

디버깅 탐지 기능 설정

디버깅 탐지 기능은 아래의 방법으로 존재한다.

  1. 디버깅 시 사용되는 ptrace 시스템 호출 차단 및 선점
  2. ppid를 확인하여 앱을 실행 시킨 프로세스가 디버깅툴인지 확인

디버깅에 사용되는 ptrace 탐지

안드로이드 앱에서 디버깅 탐지를 구현하기 위해 ptrace 시스템 호출을 감지하고 이를 차단하는 방법을 이용할 수 있다.

ptrace란?

ptrace는 리눅스 커널에서 제공하는 시스템 호출 중 하나로, 프로세스 디버깅 및 추적이 가능하다.

ptrace를 사용하면 디버거는 타깃 프로세스를 attach하여 실행을 중단하고, 메모리 내용, 레지스터 상태, 시스템 호출 등의 정보에 접근할 수 있고, 이를 통해 프로세스 상태를 관찰하고 수정할 수 있다.

따라서 이를 역 이용해 해당 함수로 안티 디버깅 기능을 구현할 수 있다.

ptrace를 활용한 디버깅 탐지 코드

가장 메인이 되는 디버깅 탐지 코드는 다음과 같다.

#include <jni.h> // JIN 헤더파일 포함 
#include <sys/ptrace.h> // ptrace 시스템 호출 사용위한 헤더파일 
#include <android/log.h> // 로깅 태그 정의 

#define LOG_TAG "AntiDebug"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

jboolean detectDebugger() { // 디버거 감지 함수 정의 
		// PTRACE_TRACEME 요청을 통해 현재 프로세스가 추적되고 있는지 확인
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
        // 만약에 false 라면 디버깅이 감지되었음을 의미
        LOGE("Debugger detected!");
        return JNI_TRUE;
    }
    // 디버거가 감지되지 않았으면 ptrace 호출을 통해 프로세스를 추적하지 않도록 설정
    ptrace(PTRACE_DETACH, 0, 0, 0);
    // 디버거가 감지되지 않았음을 반환
    LOGI("No debugger detected.");
    return JNI_FALSE; 
}

// JNI 함수 정의, Java에서 호출될 수 있도록 JNIEXPORT 및 JNICALL 키워드 사용
JNIEXPORT jboolean JNICALL Java_com_example_code_1proguard_1example_AntiDebug_detectDebugger(JNIEnv *env, jobject instance) {
    // detectDebugger 함수를 호출하여 결과 반환
    return detectDebugger();
}

위 코드에서는 ptrace 시스템 호출을 사용하여 디버거가 붙어 있는지 감지한다. ptrace(PTRACE_TRACEME, 0, 0, 0) 호출이 실패하면 디버거가 붙어 있다고 간주하고, 이를 로그에 띄우도록 하였다.

ptrace(PTRACE_TRACEME, 0, 0, 0) 함수

이는 ptrace 시스템 호출의 한 형태로 프로세스가 디버거에 의해 추적되고 있는지 확인하기 위해 사용된다. 해당 함수에서 사용되는 각 인수는 다음과 같다.

  • PTRACE_TRACEME : ptrace 시스템 호출에 사용되는 여러 명령 중 하나로, 현재 프로세스가 디버거에 의해 추적되도록 설정한다. 이 명령을 호출하면 프로세스 자신을 디버거로 설정하여 부모 프로세스가 디버거가 될 수 있도록 한다.
  • 따라서 두 번째 인자인 PID도 자기 자신을 가리키는 0을 전달하는 것이다.

해당 함수를 안티 디버깅 기능 구현을 위해 활용한 방법은 아래와 같다.

함수 활용 내용

  1. 디버거 설정: ptrace(PTRACE_TRACEME, 0, 0, 0) 호출은 현재 프로세스가 디버거에 의해 추적되도록 설정한다. 이 호출을 통해 프로세스는 부모 프로세스가 자신의 디버거가 되도록 요청한다. 일반적으로 디버거는 ptrace를 사용하여 다른 프로세스를 제어하고 디버깅할 수 있다.
  2. 디버거 감지: 하나의 process에 attach 할 수 있는 다른 프로세스는 오직 1개 이하이다. 만약 현재 프로세스가 이미 디버거에 의해 추적되고 있다면, PTRACE_TRACEME 호출은 실패하고 1을 반환합니다. 이 실패는 디버거가 붙어 있다는 것을 나타내며, 이를 통해 애플리케이션은 디버거가 감지되었음을 알 수 있다.

ptrace 활용 요약 → 프로세스 자신을 디버그로 설정하기 위해 ptrace를 사용할 때, 성공 값 0이 아닌 값이 나온다면 다른 디버거가 감지 되었다라고 생각할 수 있는 것이다.

디버깅 탐지 코드 설정 방법

  1. Tools → SDK Manager → SDK Tools 에서 NDK, CMake 설치한다.
  1. C:\Users\User\AndroidStudioProjects\’${자신의 프로젝트}‘\app\src\main 경로에 cpp 폴더 생성
  2. Android.mk 또는 CMakeLists.txt 파일 설정위에 생성한 cpp 폴더에 CMakeLists.txt 파일을 작성한다.
  3. JNI C 코드를 컴파일할 수 있도록 Android NDK 빌드 시스템을 설정한다.
cmake_minimum_required(VERSION 3.4.1)
add_library( antidebug SHARED antidebug.c )
find_library( log-lib log )
target_link_libraries( antidebug ${log-lib} )
  1. 프로젝트 내에 생성한 cpp 파일에 antidebug.c 파일을 작성한다.
  1. Java 인터페이스 작성
  2. 위의 C 코드를 Java에서 사용할 수 있도록 JNI 인터페이스를 정의한다.
package com.example.code_proguard_example;

public class AntiDebug {
    static {
        System.loadLibrary("antidebug");
    }
    public native boolean detectDebugger();
}
  1. Java 코드에서 디버거 감지 호출
  2. 이제 Java 코드에서 디버거 감지를 호출할 수 있습니다.
package com.example.antidebug;

import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkAntiDebug();
    }
    
     private void checkAntiDebug(){
        AntiDebug antiDebug = new AntiDebug();
        boolean isDebuggerDetected = antiDebug.detectDebugger();

        if (isDebuggerDetected) {
            Toast.makeText(this, "Debugger 감지됨.",Toast.LENGTH_SHORT).show();
        }
        else{
            Toast.makeText(this, "Debugger 감지됨.",Toast.LENGTH_SHORT).show();
        }
    }
}

이 코드들을 프로젝트에 통합하면 애플리케이션이 실행될 때 디버거가 붙어 있는지 확인할 수 있다.

TracerPid 활용 디버깅 탐지

TracePid란

앱의 "/proc/self/status" 파일 내용에 있는 값으로 /proc/self/status 파일은 리눅스 기반 운영 체제에서 실행 중인 프로세스에 대한 정보를 제공하는 가상 파일이다.

안드로이드 앱의 경우, 안드로이드 운영 체제는 리눅스 커널 위에서 실행되므로, 이 파일을 통해 현재 실행 중인 앱 프로세스에 대한 정보를 얻을 수 있다.

따라서 만약 디버깅툴이 앱을 실행시킨 경우 TracerPid가 0보다 큰 값을 가지므로 이를 활용하여 디버깅을 탐지한다.

antidebug .c

jboolean tracerPidDetectDebugger(){
    int TPid;
    char buf[512];
    const char *str = "TracerPid:";
    size_t strSize = strlen(str);
    jstring strDebugging = "NONE";
    FILE* file = fopen("/proc/self/status", "r");

    while (fgets(buf, 512, file)) {
        if (!strncmp(buf, str, strSize)) {
            sscanf(buf, "TracerPid: %d", &TPid);
            if (TPid != 0) {
                strDebugging = buf;
                fclose(file);
                return JNI_TRUE;
            }
        }
    }

    fclose(file);
    return JNI_FALSE;
}

JNIEXPORT jboolean JNICALL Java_com_example_code_1proguard_1example_AntiDebug_tracerPidDetectDebugger(JNIEnv *env, jobject instance) {
    return tracerPidDetectDebugger();
}

MainActivity .java

private void checkTracerPid() {
        AntiDebug antiDebug = new AntiDebug();
        boolean isDebuggerDetected = antiDebug.tracerPidDetectDebugger();

        if (isDebuggerDetected) {
            Toast.makeText(this, "tracer pid != 0 Debugger 감지됨.", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "tracer pid == 0 Debugger 감지안됨.", Toast.LENGTH_SHORT).show();
        }
    }

TracerPid 탐지 실행결과

USB 탐지 기능

USB 탐지 메인 코드

 if (intent != null && intent.getAction() != null &&
            intent.getAction().equals("android.hardware.usb.action.USB_STATE")) {
            boolean connected = intent.getBooleanExtra("connected", false);
            Log.d(TAG, "USB connected: " + connected);
 }

intent의 action을 활용해 usb 탐지를 확인하는 코드는 다음과 같다.

위 코드는 해당 함수 호출 시에만 확인할 수 있으므로 필요한 경우 리시버를 추가해서 실시간 연결 감지가 가능하다.

Receiver활용 USB 탐지 전체 코드

UsbConnectionReceiver.java

public class UsbConnectionReceiver extends BroadcastReceiver {
    private static final String TAG = "UsbConnectionReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null && intent.getAction() != null &&
                intent.getAction().equals("android.hardware.usb.action.USB_STATE")) {
            // intent의 action이 android usb action과 동일하다면 
            // intent의 connected 여부를 가져온다. 
            boolean connected = intent.getBooleanExtra("connected", false);
            Log.d(TAG, "USB connected: " + connected);
        }
    }

    // 리시버 등록
    public static void registerReceiver(Context context, UsbConnectionReceiver receiver) {
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.hardware.usb.action.USB_STATE");
        context.registerReceiver(receiver, filter);
    }

    // 리시버 해제
    public static void unregisterReceiver(Context context, UsbConnectionReceiver receiver) {
        context.unregisterReceiver(receiver);
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    private UsbConnectionReceiver usbReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        usbReceiver = new UsbConnectionReceiver();
        UsbConnectionReceiver.registerReceiver(this, usbReceiver); 
        // 메인 Activity에 동적 리시버 적용
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        UsbConnectionReceiver.unregisterReceiver(this, usbReceiver);
        // Activity Context가 유효할 때만 작동하도록 unregister 함.
    }
 }

메인 Activity에 USB 연결을 탐지하는 동적 리시버를 적용하여

메인 Activity Context가 유효한 동안 실시간 감지가 가능하도록 설정하였다.

USB 탐지 실행결과

+) 안드로이드 4대 컴포넌트 중 하나인 receiver 복습을 위한 정리

안드로이드 컴포넌트 Receiver

Receiver는 안드로이드 4대 컴포넌트 중 하나인 Broadcast Receiver를 의미한다.

Broadcast Receiver란?

안드로이드에서 Broadcast Receiver는 앱 구성 요소 중 하나로, 다른 앱이나 시스템에서 발생한 브로드캐스트 메시지를 수신하는 역할을 한다.

브로드캐스트 메시지는 시스템에서 발생하는 다양한 이벤트를 의미하며, 예를 들어 배터리 부족, 네트워크 상태 변경, 앱 설치/제거 등의 이벤트가 포함된다.

이러한 이벤트는 시스템이나 다른 앱에서 발생할 수 있으며, Broadcast Receiver는 이러한 이벤트를 수신하여 적절한 처리를 할 수 있다.

Broadcast Receiver는 안드로이드의 시스템 브로드캐스트를 수신하는 것 외에도, 개발자가 정의한 앱에서 발생시키는 브로드캐스트 메시지도 수신할 수 있다.

이를 통해 앱 내에서 이벤트를 처리하거나, 다른 앱에 메시지를 전달하는 등의 다양한 작업을 수행할 수 있다.

Broadcast Receiver

  • 시스템이나 다른 앱에서 보내는 브로드케스트 메시지를 받아서 처리한다
  • 디자인 패턴 중 publish-subscribe 형태 처럼 한쪽에서는 이벤트를 제공하기만 하고 한쪽에서는 이벤트를 받기만 한다.
  • 앱들끼리도 미리 사전에 정의한 Action을 주고 받을 수 있다.

등록 방식에 따른 종류

정적 리시버

  • AndroidManifest.xml 파일에 등록되며 라이프 사이클과 무관하게 동작한다.
  • 앱이 설치되면 즉시 사용 가능하며 등록과 해지가 자유롭지 못하다.

동적 리시버

  • Activity와 같은 컴포넌트에서 프로그래밍적으로 등록하며 라이프사이클 내에서 등록 및 삭제 처리가 필요하다.
  • 해당 코드가 실행될 때 사용 가능하며 코드 내에서 필요에 따라 등록 및 삭제가 가능하다.
  • USB 탐지 기능에서 동적 리시버를 사용하였다.
  • Android 4대 컴포넌트 중 Broadcast Recevier만 동적으로 등록할 수 있다.
728x90
저작자표시 (새창열림)

'Android > 공부' 카테고리의 다른 글

[Android] XML 과 Compose 렌더링 방식의 차이  (0) 2024.09.23
[Android/공부] 안드로이드 라이브러리 / AAR 만들기  (0) 2024.07.17
[Android/공부] 안드로이드 소스 코드 난독화 R8 / Proguard  (1) 2024.07.10
[Android] Flow란? LiveData와 Flow 비교  (0) 2023.11.13
[Android/코루틴] 2.코루틴의 기본요소들을 알아보자  (0) 2023.09.21
'Android/공부' 카테고리의 다른 글
  • [Android] XML 과 Compose 렌더링 방식의 차이
  • [Android/공부] 안드로이드 라이브러리 / AAR 만들기
  • [Android/공부] 안드로이드 소스 코드 난독화 R8 / Proguard
  • [Android] Flow란? LiveData와 Flow 비교
hyeon.s
hyeon.s
이유있는 코드를 짜자

티스토리툴바