변경이력: History

일자 구분 변경점 비고
2025.05.11 최초작성

사전 정보: Prev Information

🪄
Notice : 본 과정은 개발 및 컴퓨터 활용에 관련된 지식을 어느 정도 보유하고 있어야 원활한 이해 및 학습 진행이 가능합니다.
🪄
아래의 보안검증 우회 실습은 ANDITER 와 FRIDA 가 설치 및 구성이 완료되어야 진행이 가능합니다. 만약 실습 환경 구성이 되지 않았다면, 앞서 "Anditer : Android 보안 코드 우회 실습 환경 구축하기" 게시글을 참고하세요.

Anditer 와 Frida 가 정상적으로 설치 및 구성이 되었다면 ANDITER 앱을 실행 한 뒤 frida-ps -Ua 명령어를 통해 다음과 같이 ANDITER 프로세스 목록이 출력 됩니다.

D:\frida> uv run frida-ps -Ua
 PID  Name      Identifier
----  --------  ----------------------
1234  ANDITER   com.playground.anditer

대상 기기(Nox 등)에 frida 서버가 정상 동작하고 있고, 연결에 이상이 없다면 위와 같이 결과 값이 나온다.

요약 및 실습 목표: Summary or Goal

코드 분석을 통해 루팅 검증 우회 Frida 스크립트를 작성해보고, 실습을 통해 실제로 우회 과정과 결과를 직접 확인해 봅니다.

  • 함수 자체가 반환하는 값을 항상 False로 변조하여 루팅 검증 우회
  • Runtime.getRuntime().exec(...) 내 실행되는 명령어를 변조하여 루팅 검증 우회
  • InputStreamReader 생성자 처리 과정을 변조하여 루팅 검증 우회
  • BufferedReader 생성자 처리 과정을 변조하여 루팅 검증 우회

설명 또는 : Descriptions

루팅 여부 탐지 기법 중 설치된 패키지 목록을 기반으로 한 탐지 기법을 이해하고, 우회 원리와 방법을 살펴봅니다.

실제 환경에서는 패키지 기반 검증 한 가지 방법만으로 루팅 여부 탐지가 이루어지지는 않으나 실습 과정을 통해 방식을 이해하고 우회 할 수 있는 방법을 학습하여, 분석 대상 애플리케이션에 접근하기 위한 방법 탐색에 도움이 될 수 있을 것입니다.

실습 과정: Step By Step

Step 1) ANDITER 실행 후 Bypass Command Execution 의 설명 아이콘을 선택, 루팅 탐지 원리 및 방법에 대한 설명을 참고하고 Check 버튼을 눌러 응답 상태를 확인합니다. 'su' 명령어가 있기 때문에 Rooting 탐지 우회가 되지 않음으로서 Fail 응답이 나타나는 모습을 확인할 수 있습니다.

'su' 명령이 동작 되는 것을 탐지하여 Fail 응답이 반환 되는 상태

Step 2) Bytecode Viewer를 실행한 뒤에 Command Execution의 Check 부분이 호출하는 부분을 추적해 보면 다음과 같이 isCheckRootingExec() 메소드(함수)부분을 찾을 수 있습니다.

이 메소드(함수) 역시 반환 값이 Boolean 형으로 항상 False 값을 반환하도록 하여 루팅을 우회할 수 있도록 앞서 실습 과정에서 사용했던 다음과 같은 코드 블럭을 활용하여 Frida 스크립트를 작성해 볼 수 있을 것입니다.

{class}.{method(function)}.implementation = function () {
  {code block}
}

함수를 직접적으로 변조하지 않는 경우를 고려하면, Runtime.getRuntime().exec(...), InputStreamReader 및 BufferedReader 처리 과정을 변경하는 것으로서 Frida 스크립트를 작성, 우회를 시도해 볼 수 있을 것입니다.

Runtime.getRuntime().exec(new String[]{...}) 부분에서의 동작은 문자열 배열안의 값을 이용해 새로운 Process 를 만들어 수행하고 이에 대한 결과를 활용하므로 Runtime 클래스와 ProcessBuilder 클래스를 활용해야 할 수 있고, 해당 처리 부분은 다양한 경우에 활용될 가능성이 있기 때문에 new String[]{...} 내 값이 내가 원하는 명령어가 존재할 경우에만 처리되도록 해야 문제가 발생하지 않을 수 있습니다. 이를 고려해 작성을 하면 좋고, 참고할 만한 frida 코드 블럭은 다음과 같습니다.

var Runtime = Java.use("java.lang.Runtime");
var ProcessBuilder = Java.use("java.lang.ProcessBuilder");
Runtime.exec.overload('[Ljava.lang.String;').implementation = function (cmdArray) {
  {code block}
  return this.exec(cmdArray);
};

InputStreamReader 및 BufferedReader 부분은 생성자 호출 영역을 통해 후킹을 시도해 볼 수 있는 만큼 다음과 같은 코드 블럭을 활용해 볼 수 있을 것입니다.

var InputStreamReader = Java.use("java.io.InputStreamReader");
InputStreamReader.$init.overload("java.io.InputStream").implementation = function (argument) {
  {code block}
}

여기까지 진행해 볼 때 만약 직접적으로 스크립트 작성이 어려우신 분들이 계시다면 제가 작성하여 아래 첨부해 놓은 스크립트를 다운로드 받아 과정을 따라가 주시면 되겠습니다.

Step 3) 직접 우회를 위한 스크립트를 작성하거나, 앞서 제공된 frida 스크립트를 사용할 경우에는 변조(후킹)과정을 보기 위해 각 변수를 설정합니다.

Step 4) 우리는 uv를 이용한 환경 구성을 수행했었기 때문에 uv run frida -U -N com.playground.anditer -l anditer_bypass_command_execution.js 와 같이 명령어를 통해 frida 스크립트를 실행합니다.

Step 5) 이제 Anditer 가 실행되고 있는 에뮬레이터 환경으로 돌아와 Bypass Packages 의 Check 부분을 선택하면 명령어 실행 기반 루팅 여부 탐지 부분이 우회 되어 Success 응답 값을 확인할 수 있습니다.

다만 처리된 로그를 확인하면 Function 자체의 동작을 변조했기 때문에 Runtime.getRuntime().exec(...), InputStreamReader 및 BufferedReader 처리 과정과 연관된 로그가 나타나지 않는 모습도 함께 확인됩니다.

Step 6) 이제 Function 자체 변조를 비활성화 하기 위해 functionHooking 변수를 false로 변경한 뒤 다시 스크립트 동작을 실행하고 지켜봅니다.

Step 7) 다시 Anditer 가 실행되고 있는 에뮬레이터 환경으로 돌아와 Bypass Packages 의 Check 부분을 선택하면 Runtime.getRuntime().exec(...), InputStreamReader 및 BufferedReader 처리 과정과 연관된 Hooking 로그를 확인해 볼 수 있습니다.

생각해볼 점

우회 관점에서는 다음과 같은 점이 고려될 것입니다.

  • 함수 자체에 접근하여 처리 결과를 직접적으로 변경하여 영향을 줄 것인가?
  • 함수 내부에서 사용되고 있는 처리 과정을 변경하여 간접적으로 영향을 줄 것인가?

처리하는 과정에 있어서도 단일적으로 사용되고 있는 부분이라면 영향도가 크지 않을 것이고, 만약 내부에서 처리되는 로직을 처리하는데 있어 해당 부분이 예상하지 못하게 다양한 부분에서 호출되는 부분이라면 적절한 대응을 못할 경우에는 프로그램이 충돌 또는 오류 등으로 정상동작이 되지 않는 경우가 생길 수 있을 것입니다.

이 실습에서는 다양한 경우를 고려해보기 위해 함수 자체 외에도 다양한 접근 방법을 제시하고 실습하고 있으나, 실무에서는 적절한 선택이 필요한 순간이 올 것임을 염두에 두고 실습해야 할 것입니다.