붉은외계인 Dev Blog
외계인의 성장 기록

🔒 Security/Mobile

외계인의 관점에서 정리한 각종 지식들을 탐험해보세요 🚀

📱

[붉은외계인] Mobile - Frida Detected Java 코드

서론프로젝트를 진행하는 과정에서, 정리해두면 좋겠다는 부분이 있어서 오랜만에 글을 쓴다글을 안 쓴지 이렇게 오래되었을 줄이야..본론으로 넘어가서, 오늘 작성할 내용은 Java 코드를 사용하여, Frida Server를 감지하는 코드이다해당 코드를 여러 번 시도해본 결과, 특징과 주의사항은 아래와 같다1. Android 14에서는 작동하지 않는다 ( 현재, Android 9까지 작동되는 것을 확인하였다 )2. Runtime.getRuntime.exec()를 사용하는 것이 아닌, ProcessBuilder 객체를 사용해야한다3. ProcessBuilder에 Argument를 줄 때, "ps -a" 가 아닌, "ps", "-a" 이런식으로 주어야 한다 본론으로 들어가보자  - Runtime.getRuntime..

📱

[붉은외계인] Mobile - OWASP UnCrackable L3

2024.03.05 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L2 [붉은외계인] Mobile - OWASP UnCrackable L22024.03.04 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L1 [붉은외계인] Mobile - OWASP UnCrackable L1 Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다 OWASP에서는 래퍼런스redalien.tistory.com이전 Level 2단계에 이어서, 3단계를 진행하겠다바로 들어가보자! 시작해당 단계 또한, Rooting을 감지하면 앱을 종료시켜버린다매 단계에서 진행했던 Rooting 우회를 시도해보겠..

📱

[붉은외계인] Mobile - OWASP UnCrackable L2

2024.03.04 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L1 [붉은외계인] Mobile - OWASP UnCrackable L1Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다 OWASP에서는 래퍼런스로 Android, iOS 상당히 많은 앱을 제공하고 있고, 앱 외에도 해킹 도구라던지 각종 좋은 정보들redalien.tistory.com 1단계에 이어서, 2단계를 알아보겠다 참고로, 2단계에서의 루팅 우회는 1단계에서와 동일하기 때문에, 이번 포스트에서 루팅 우회는 넘어갈 것이다 분석루팅 우회를 한 후, 어플에 접속을 해보면 위의 그림과 같다 이것 또한 Secret String을 입력해야한다j..

📱

[붉은외계인] Mobile - OWASP UnCrackable L1

Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다OWASP에서는 래퍼런스로 Android, iOS 상당히 많은 앱을 제공하고 있고, 앱 외에도 해킹 도구라던지 각종 좋은 정보들을 제공하고 있다아래 링크를 첨부할테니, 꼭 한번씩 들어가보길 바란다https://mas.owasp.org/MASTG/apps/ Reference applications - OWASP Mobile Application SecurityReference applications The applications listed below can be used as training materials. Note: only the MASTG apps and Crackmes are tested..

📱

[붉은외계인] Mobile - FridaLab 풀이

들어가며Frida를 배우고 난 후, 연습을 위하여 FridaLab 을 풀어보았다 1 ~ 5단계는 쉽기 때문에, 해당 포스트에서는 6 ~ 8 단계만 다루고 있다나머지 Solution Scripts는 아래 Github에 올려놓았으니, 참고하길 바란다https://github.com/RedAlien00/FridaLab GitHub - RedAlien00/FridaLabContribute to RedAlien00/FridaLab development by creating an account on GitHub.github.com 서론 FridaLab을 풀어볼 때, 외우는것이 아닌 왜 이러한 메소드를 썼고, 이러한 코드를 짰는가에 포인트를 두고 진행하였다그리고 많은 풀이들을 살펴보고, 이리저리 바꾸어가며 나의 걸..

📱

[붉은외계인] Mobile - smali 코드 분석 1 with KGB Messenger

KGB Messenger CTF를 하던 중에, 처음으로 smali 코드 분석을 하게 되었다smali코드를 이해할 수 있다면 추후, 취약점 분석에 많은 도움이 될 거 같아서, 오늘 공부한 내용을 정리하려한다 시작해보자 Java 소스 코드아래는 원본 Java 소스 코드이다 ( 디컴파일은 jadx-gui를 통해 하였다 )package com.tlamb96.kgbmessenger;import android.content.DialogInterface;import android.content.Intent;import android.os.Bundle;import android.support.p022v7.app.ActivityC0494c;import android.support.p022v7.app.DialogInte..

📱

[붉은외계인] Mobile - Diva Hardcoding Issues - Part 2

Nox 에뮬레이터에, 취약한 모바일 앱인 diva를 통하여 공부를 하던 도중 상당히 재미있었던 부분을 정리할려고 한다 시작해보자 Hardcoding Issues - Part 2 Part 1은 정말 쉬웠으나, Part 2는 jadx를 통해 디컴파일한 소스를 들여다보는 과정 + 추가 작업이 필요하다 Hardcode2Activity 클래스에서는 DivaJni 클래스의 access 함수를 사용한다 이러한 access 함수는 native 키워드가 사용되었기 때문에, JNI를 통해서 라이브러리를 호출 할 때 구현될 것이다 어떤 라이브러리를 호출하는지 살펴보면 divajni 라이브러리를 호출하고 있다 라이브러리 jadx를 통해 컴파일 한 후, lib 디렉토리를 살펴보면 각 아키텍쳐마다 사용할 수 있게끔, 동적 라이브..

🛸
축하합니다!
포스트를 모두 읽으셨습니다! 👽✨
⏱️ 읽기 시간
계산중...
소요되었습니다
🔒 Security/Mobile

[붉은외계인] Mobile - Frida Detected Java 코드

🗓️ 2024. 5. 9. 15:21
⏱️ 계산 중...
반응형

 

서론

프로젝트를 진행하는 과정에서, 정리해두면 좋겠다는 부분이 있어서 오랜만에 글을 쓴다
글을 안 쓴지 이렇게 오래되었을 줄이야..

본론으로 넘어가서, 오늘 작성할 내용은 Java 코드를 사용하여, Frida Server를 감지하는 코드이다
해당 코드를 여러 번 시도해본 결과, 특징과 주의사항은 아래와 같다

1. Android 14에서는 작동하지 않는다 ( 현재, Android 9까지 작동되는 것을 확인하였다 )
2. Runtime.getRuntime.exec()를 사용하는 것이 아닌, ProcessBuilder 객체를 사용해야한다
3. ProcessBuilder에 Argument를 줄 때, "ps -a" 가 아닌, "ps", "-a" 이런식으로 주어야 한다

 

본론으로 들어가보자

 


 

- Runtime.getRuntime.exec()을 사용하는 방식 

 public static boolean isFridaServerOn(){
        boolean bool = false;

        try {
            Process process = Runtime.getRuntime().exec("netstat");
            if(process != null){
                InputStream is = process.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                String line;
                if( (line = reader.readLine()) != null ){
                    Log.i(TAG, "isFridaServerOn : " + line);
                    if(line.contains("frida")){
                        bool = true;
                    }
                }
            }
        } catch(Exception e){
            e.printStackTrace();
        }
        Log.i(TAG, "isFridaServerOn : " + bool);
        return bool;
    }

 

해당 방식을 사용하면 정확하게 인터넷 연결 내용을 보여주지 않는다 

 

위에는 Runtime.getRuntime.exec("ps", "-a")를 사용한 결과이다
이것 또한 제대로 보여주지 않는다


하지만 ProcessBuilder는 다르다

 


 

- ProcessBuilder를 사용하는 방법

public static boolean test() {
        boolean bool = false;

        try {
            ProcessBuilder processBuilder = new ProcessBuilder("netstat");
            Process process = processBuilder.start();

            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;

            if( (line = reader.readLine()) != null ){
                while ((line = reader.readLine()) != null) {
                    Log.i("Detect", "test() : " + line );
                    if(line.contains("frida")){
                        Log.i("Detect", "test() : " + "frida Server Detected - " + line );
                        bool = true;
                        break;
                    }
                }
            }
            // 프로세스 종료
            process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
        Log.i("Detect", "tesT() : " + String.valueOf(bool) );
        return bool;
    }

 

frida server를 실행한 후, ProcessBuilder를 사용하면 위와 같이, frida server를 detect 할 수 있다
위에서 언급하였듯, Android 14에서는 작동하지 않는다 Android 9까지는 작동을 확인하였다

 


 

마치며

 

현재, 취약한 앱을 개발하는 개인 프로젝트를 진행하고 있다


Java코드 단에서 Root detection과 frida detection을 구현하고 있는 단계인데,
의외로 구글링해서 나온 코드들은 Android 14버전에서 제대로 detection을 못해주는 경우가 많았다

Java 코드 단에 구현이 마무리 되면 JNI를 통해, Native단에서도 detection 코드를 구현할 계획이다
화이팅 !@!@

반응형
🛸
축하합니다!
포스트를 모두 읽으셨습니다! 👽✨
⏱️ 읽기 시간
계산중...
소요되었습니다
🔒 Security/Mobile

[붉은외계인] Mobile - OWASP UnCrackable L3

🗓️ 2024. 3. 12. 16:25
⏱️ 계산 중...
반응형

2024.03.05 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L2

 

[붉은외계인] Mobile - OWASP UnCrackable L2

2024.03.04 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L1 [붉은외계인] Mobile - OWASP UnCrackable L1 Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다 OWASP에서는 래퍼런스

redalien.tistory.com

이전 Level 2단계에 이어서, 3단계를 진행하겠다

바로 들어가보자!

 


 

시작

해당 단계 또한, Rooting을 감지하면 앱을 종료시켜버린다

매 단계에서 진행했던 Rooting 우회를 시도해보겠다
먼저, 해당 AlertDialog에 세팅된 문구를 출력하는 메소드를 찾아보겠다

 

위에 코드들은 모두 sg.vantagepoint.uncrackable3.MainActivity 클래스에 존재하는 코드이다

코드를 살펴보면 AlertDialog를 출력하는 showDialog 메소드를 만들어 사용하고 있다
그 아래 그림의 if 조건문은 MainActivity.onCreate 메소드내에 쓰여진 코드이다

해당 조건문을 충족하면 showDialog에 String을 전달하며, showDialog메소드를 실행한다
해당 조건문에 사용된 메소드들을 살펴보자

 


 

checkRoot 메소드

먼저, RootDetection 클래스의 checkRoot 메소드들을 살펴보자

코드를 살펴보면, Level 1 ~ 2에서 다뤘던 구조와 비슷하다
이전 포스트에서 다뤘기 때문에, 해당 클래스의 메소드를 우회하는 내용은 생략하겠다 

 


 

isDebuggable 메소드

// 해당 상수의 값은 2
ApplicationInfo.FLAG_DEBUGGABLE

다음은 IntergrityCheck 클래스의 isDebuggable 메소드를 살펴보자

flag값과 2를 AND 비트연산하고 있다
참고로, 여기서 2인 이유는 위의 쓰여진 FLAG_DEBUGGABLE 상수 대신 바로 2를 써넣은것이다

이게 0이 아니라면 Debug 모드로 판단하고 true를 반환하고, 그 반대는 false를 반환한다

 


 

AsyncTask 클래스

추가로, onCreate 메소드가 실행될 때, AsyncTask 클래스를 사용하여 비동기 작업을 하고 있다
내용을 살펴보자

Debug.isDebuggerConnected() 메소드는 디버거가 연결되어 있지 않으면 false를 반환하는데
false를 반환할 때, 0.1초 sleep에 들어간다 그리고 while문으로 인하여 이것을 계속 반복한다

이러한 과정은 AsyncTask 사용으로 인하여, 비동기로 처리하면서 계속 검사하고 있는 것이다

만약, 디버거가 연결되어 있으면 true를 반환하면서 onPostExecute()를 실행한다
onPostExecute() 메소드 안에는 위에서 살펴본 showDialog메소드에 String을 전달하고
System.exit() 메소드를 실행하여 앱을 종료시켜버린다

 

참고로, 현재 AsyncTask는 deprecated 되었기 때문에 다시 보기는 힘들 것이다


이제 지금까지 알아본 매소드들을 우회하는 코드를 짜보겠다

 


 

우회 코드 1

Java.perform(function(){
  console.warn("[+] Frida Start ...");

  let system = Java.use("java.lang.System")
  system.exit.implementation = function(){
    console.warn("Avoid Exit !");
  }

  let Activity = Java.use("sg.vantagepoint.util.RootDetection")
  Activity.checkRoot1.implementation = function(){console.warn("Avoid checkRoot1() !"); return false}
  Activity.checkRoot2.implementation = function(){console.warn("Avoid checkRoot2() !"); return false}
  Activity.checkRoot3.implementation = function(){console.warn("Avoid checkRoot3() !"); return false}

  let Activity2 = Java.use("sg.vantagepoint.util.IntegrityCheck")
  Activity2.isDebuggable.overload("android.content.Context").implementation = function(){
    console.warn("Avoide isDebuggable() !");
    return false
  }

  let Activity3 = Java.use("android.os.Debug")
  Activity3.isDebuggerConnected.implementation = function(){
    console.warn("Avoid isDebuggerConnected() !");
    return false
  }
  console.warn("[+] Done !");
})

하지만 해당 코드를 실행하여도, 우회는 실패한다
다시 jadx를 통해 코드를 살펴보자

 


 

log 분석

코드를 살펴보던 중, 위의 그림과 같이 Log를 출력하는 코드를 찾았다
간단하게 보자면, CRC값 체크를 통해 데이터 변조 여부를 파악하는 것 같다
다른것들을 더 살펴보기 전에, logcat을 사용하여 어떤 내용이 log로 출력되는지 체크해 보겠다

위의 코드를 통하여, Priority는 V를 사용하고, Tag는 UnCrackable3를 사용한다는 것을 알았으니,
logcat의 필터 표현식을 사용하여 체크해보겠다

 

필터 표현식 마지막에 쓰인 *:S는 앞에 표현식 외 내용들은 출력하지 않겠다는 의미이다

필터 표현식 결과, 위와 그림과 같이 나오는 것을 확인할 수 있다
libfoo.so 파일과 연관되어 있는 것 같으니, 사용중인 so파일을 추출한 후, IDA로 열어보겠다

 

2024.03.05 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L2

 

[붉은외계인] Mobile - OWASP UnCrackable L2

2024.03.04 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L1 [붉은외계인] Mobile - OWASP UnCrackable L1 Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다 OWASP에서는 래퍼런스

redalien.tistory.com

사용중인 so 파일을 찾는 방법은 Level 2 단계에 자세하게 기록했기 때문에 여기서는 생략하겠다

 


 

libfoo.so 파일 분석

jadx를 통해서 살펴보았을 때는 Tampering detected! 라는 문자열 출력 메소드를 찾지 못하였다
그래서 libfoo.so 파일에서 Tampering detected! 라는 문자열을 찾아볼 것이다

 

IDA의 Strings 기능과 External Reference 기능을 사용하여, 해당 문자열이 사용되는 곳을 검색하였다

 

그 결과, sub_3080() 메소드에서 사용되는 것을 확인하였다

해당 메소드의 이름이 sub인 이유는 대상 함수의 심볼이 죽어있어, IDA에서 자체적으로 이름을 붙인 것이다
코드를 살펴보면, do while 안에 strstr() 함수를 사용하여 frida와 xposed 문자열이 있는지 검사하고 있다 

해당 문자열이 없다면, 계속 while문을 돌겠지만, 문자열이 있다면 v2에 문자열을 할당한 후,
__android_log_print() 함수를 실행한다

결론적으로, strstr() 함수가 0을 반환하도록 하면 해당 함수를 우회할 수 있을 것이다
strstr() 함수를 우회하는 코드를 추가하면 아래와 같다

 


 

우회 코드 2

  Java.perform(function(){
    console.warn("[+] Frida Start ...");

    Interceptor.attach(Module.getExportByName("libc.so", "strstr"), {
      onEnter: function(arg){
        let arg1 = Memory.readUtf8String(arg[0])
        this.frida = 0
        if(arg1.indexOf("frida") !== -1){ this.frida = 1}
      },
      onLeave: function(retval){
        if(this.frida){
          retval.replace(0);
        }
      }
    })
    console.warn("Avoid strstr()");

    let system = Java.use("java.lang.System")
    system.exit.implementation = function(){
      console.warn("Avoid Exit !");
    }

    let Activity = Java.use("sg.vantagepoint.util.RootDetection")
    Activity.checkRoot1.implementation = function(){console.warn("Avoid checkRoot1() !"); return false}
    Activity.checkRoot2.implementation = function(){console.warn("Avoid checkRoot2() !"); return false}
    Activity.checkRoot3.implementation = function(){console.warn("Avoid checkRoot3() !"); return false}

    let Activity2 = Java.use("sg.vantagepoint.util.IntegrityCheck")
    Activity2.isDebuggable.overload("android.content.Context").implementation = function(){
      console.warn("Avoid isDebuggable() !");
      return false
    }

    let Activity3 = Java.use("android.os.Debug")
    Activity3.isDebuggerConnected.implementation = function(){
      console.warn("Avoid isDebuggerConnected() !");
      return false
    }
    console.warn("[+] Frida Done !");
})

해당 코드를 실행하면, AlertDialog 알림창 뜨는 것 없이 바로 접속되는 것을 확인할 수 있다
참고로, prompt에는 Avoid isDebuggerConnected() 가 계속 출력 될 것이다
입맛에 맞게, console.warn()을 삭제하여도 좋다

코드에서 libfoo.so 가 아닌 libc.so 파일을 로드한 이유는 libfoo.so 파일을 로드하면 strstr()를 찾을 수 없다는
에러가 발생하였기 때문이다 구글링을 했지만 에러 원인을 정확하게 이해하지는 못했다

◆ 모듈 이름을 모르면 null을 넣으라고 공식 문서에 명시되어 있긴 하나, 성능 저하 때문에 가급적 지양하라고 한다

나름 이해한 결과로는, strstr은 표준 라이브러리인 libc.so 에 속해 있기 때문에
libfoo.so 파일이 로드되기전이라도, libc에 연결하면 strstr()을 사용할 수 있다 (?) 이다

 

해당 원인을 드디어 찾았다 !

frida -U -f owasp.mstg.uncrackable3 -l 123.js

스크립트를 실행할 때는 위와 같이 입력할 것이다 여기서 포인트는 -f 옵션이다

-f 옵션을 사용하면 앱이 실행되기 전에, 스크립트를 먼저 적용한 다음에 앱을 실행한다
이것은 앱이 모듈을 로드하기도 전에 스크립트를 적용하는 것과 같다

즉, libfoo.so 를 로드하기도 전에 스크립트를 먼저 적용하기 때문에,
스크립트에 libfoo.so 모듈을 검색하는 코드가 있을 경우, 에러가 발생하는 것이다

하지만 우리가 찾으려는 strstr() 함수는 libfoo.so에 속해있기도 하나, 
표준 라이브러리인 libc.so에도 속해있다

그렇기 때문에, 로드되기 전인 libfoo.so에 접근하면 에러가 발생하지만
libc.so을 통한다면 strstr() 함수에 접근 할 수 있다

 


 

분석

우회에 성공하였으니, 이제 Secret String을 찾아보자

check_code(obj)을 살펴보면 사용자 입력값인 obj를 넣고 있다

CodeCheck.check_code()가 true가 나오기 위해서는, Native 메소드인 bar()가 true가 나와야한다
다시 한 번 libfoo.so를살펴보자

참고로, IDA는 x86 so 파일의 bar()함수 로드에 실패하기 때문에 Ghidra를 사용하였다

 

해당 코드는 Java_sg_vantagepoint_uncrackable3_CodeCheck_bar 의 중요 부분이다

do while문을 사용하여, if (*(byte *)(iVar1 + uVar3) != (*pbVar5 ^ local_40[uVar3])) 가 같지 않다면
goto LAB_00013456 로 이동하고, 같다면 uVar3과 pbVar5를 1씩 증가시킨다

그리고 uVar3가 24와 같아지기전까지 해당 과정을 반복하다가,
같아지면 do while문을 빠져나가 그 아래 내용을 수행한다 

가장 먼저, &DAT_0001601c 주소의 내용을 pbVar5에 할당하고 있는데 이거 먼저 알아보겠다

0x18를 10진수로 바꾸면 24이다

 

이러한 &DAT_0001601c 는 init 함수에서 strncpy()를 사용하여,
init함수에 들어온 Argument에서 길이 24에 해당하는 문자를 &DAT_0001601c 에 복사하고 있다

 

이러한 init함수는 jadx를 통해 사용을 확인해본결과, 길이 24인 pizzapizzapizzapizzapizz 문자열을 
Argument로 넣고 있다

즉, &DAT_0001601c에는 pizzapizzapizzapizzapizz  문자열이 복사될 것이다
그렇기에, 해당 주소를 위의 그림과 같이 pizza로 이름을 변경하였다

 

다음으로, 살펴볼 것은 local_40 변수이다

조건문을 보면 pbVar5와 local_40의 인덱스와 xor연산을 하고 있는데,
이러한 local_40은 먼저, FUN_00010fa0 함수의 Argument로 전달되어, 특정 연산을 하고 있다

아마 특정 값을 생성하여 local_40에 할당하는 것처럼 보인다
해당 함수를 살펴보자

 

먼저 FUN_00010fa0 함수를 decompile을 통해 살펴보면 param_1이라는 포인터를 Argument받고 있다
또한 1525 줄이나 될 정도로, 코드의 길이가 길다

중요한점은 이렇게 긴 연산을 마무리하면, param_1 포인터가 가리키는 메모리 주소에
해당 연산 값이 저장될 거라는 점이다

 


param_1을 마우스로 클릭하면, 위와 같이, 00010fa0 주소를 가리키는데,
이것은 해당 포인터의 주소가 00010fa0 이라는 것이다

위에서 언급하였듯, 연산이 끝나면 포인터가 가리키는 메모리 주소에 연산 값이 저장된다고 하였다
즉, 연산이 끝난 뒤에 00010fa0 을 살펴보면 해당 연산 값을 알 수 있다는 것이다


또한 위에서 알아본 bar함수의 do while문은 uVar3의 값이 24가 되면 빠져나갔기 때문에 
FUN_00010fa0 함수는 24 길이에 해당하는 연산 값을 만들거라 예상할 수 있다

해당 연산값을 추출하는 코드를 짜보겠다

 


 

Secret 값 추출 코드

// 해당 메모리 위치에서, 길이 24만큼 읽어들이자
setTimeout(function(){
let length = 24
Interceptor.attach(Module.findBaseAddress("libfoo.so").add(0xFA0), {
  onEnter : function(arg){this.location = arg[0]},  // 현재 메모리위치 기록
  onLeave : function(retval){
    let buffer = Memory.readByteArray(this.location, length )  // ArrayBuffer를 반환
    // ArrayBuffer에 접근하기 위한 Uint8Array 객체 생성
    let secretArrayBuffer = new Uint8Array(buffer)

    console.warn(`Secret Array Byte : ${secretArrayBuffer}`);
  }
})
}, 2000)

해당 코드를 실행한 후, 아무 문구나 입력하면 위와 같이 값을 반환하는 것을 확인할 수 있다
즉, FUN_00010fa0 함수의 반환값은 위와 같다는 것이다

참고로, ArrayBuffer에는 직접 접근할 수 없기 때문에 Uint8Array 혹은 DataView를 통해 접근해야 한다

 

이러한 반환값의 각 인덱스와 pbVar5을 24번 동안 xor 연산하고 있기 때문에
우리 또한 pbVar5와  반환값을 24번 xor 연산하면 어떠한 값을 얻을 수 있을 것이다

위에서 알아보았듯, pbVar5는 pizzapizzapizzapizzapizz 문자열 이였다
해당 문자열과 반환값을 xor 하는 연산 그리고 최종 코드를 작성해보겠다

 


 

최종 코드

Java.perform(function(){
  console.warn("[+] Frida Start ...");

  Interceptor.attach(Module.getExportByName("libc.so", "strstr"), {
    onEnter: function(arg){
      let arg1 = Memory.readUtf8String(arg[0])
      this.frida = 0
      if(arg1.indexOf("frida") !== -1){ this.frida = 1}
    },
    onLeave: function(retval){
      if(this.frida){
        retval.replace(0);
      }
    }
  })
  console.warn("Avoid strstr()");

  let system = Java.use("java.lang.System")
  system.exit.implementation = function(){ console.warn("Avoid Exit !");}

  let Activity = Java.use("sg.vantagepoint.util.RootDetection")
  Activity.checkRoot1.implementation = function(){console.warn("Avoid checkRoot1() !"); return false}
  Activity.checkRoot2.implementation = function(){console.warn("Avoid checkRoot2() !"); return false}
  Activity.checkRoot3.implementation = function(){console.warn("Avoid checkRoot3() !"); return false}

  let Activity2 = Java.use("sg.vantagepoint.util.IntegrityCheck")
  Activity2.isDebuggable.overload("android.content.Context").implementation = function(){
    console.warn("Avoid isDebuggable() !");
    return false
  }
    setTimeout(function(){
    let xorkey = "pizzapizzapizzapizzapizz"
    let length = 24

    Interceptor.attach(Module.findBaseAddress("libfoo.so").add(0xFA0), {
      onEnter : function(arg){this.location = arg[0]},  // 현재 메모리위치 기록
      onLeave: function(retval){
        let buffer = Memory.readByteArray(this.location, length )  // ArrayBuffer를 반환
        let secretArrayBuffer = new Uint8Array(buffer)

        console.warn(`Secret Array Byte : ${secretArrayBuffer}`);

        let xor_result = []
        for(let i=0; i<length; i++){
          xor_result[i] = String.fromCharCode(secretArrayBuffer[i] ^ xorkey.charCodeAt(i))
        }
        console.warn(`xorkey : ${xorkey}`);
        console.warn(`xor_result : ${xor_result.join(" ")}`);
      }
    })
  }, 2000)
})

 

해당 코드를 실행하여 얻은 문자열을 입력하면 성공하는 것을 확인할 수 있다

 


 

마무리

해당 풀이를 진행하고, 습득하는데까지 얼추 3일정도 걸린 거 같다

일단 어셈블리어와 C언어를 하나도 모르는 상태에서 진행하다보니, 코드가 전혀 읽히지 않았다
또한 IDA와 Ghidra를 다루는거에서도 많은 시간을 썼다 

 

바로 Uncrackable 4단계를 풀려고 하였으나, 해당 풀이를 통해 부족한 부분을 파악할 수 있었기 때문에,
다음 공부는 개인적으로 밀렸던 공부와 C언어를 하지 않을까 싶다

얼른 해당 공부를 마치고 4단계를 풀어보고 싶다! 

끝 !

 


 

참고

https://www.youtube.com/watch?v=bJgR5PKv2t0&t=690s

https://nibarius.github.io/learning-frida/2020/06/05/uncrackable3

 

 

반응형
🛸
축하합니다!
포스트를 모두 읽으셨습니다! 👽✨
⏱️ 읽기 시간
계산중...
소요되었습니다
🔒 Security/Mobile

[붉은외계인] Mobile - OWASP UnCrackable L2

🗓️ 2024. 3. 5. 16:31
⏱️ 계산 중...
반응형

2024.03.04 - [Mobile] - [붉은외계인] Mobile - OWASP UnCrackable L1

 

[붉은외계인] Mobile - OWASP UnCrackable L1

Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다 OWASP에서는 래퍼런스로 Android, iOS 상당히 많은 앱을 제공하고 있고, 앱 외에도 해킹 도구라던지 각종 좋은 정보들

redalien.tistory.com

 

1단계에 이어서, 2단계를 알아보겠다 


참고로, 2단계에서의 루팅 우회는 1단계에서와 동일하기 때문에, 
이번 포스트에서 루팅 우회는 넘어갈 것이다

 


 

분석

루팅 우회를 한 후, 어플에 접속을 해보면 위의 그림과 같다 

이것 또한 Secret String을 입력해야한다
jadx를 통해 분석을 해보자

 

 

코드를 살펴보면, verify 메소드에서 사용자가 입력한 값을 obj 변수에 넣은 후,
this.m.a(obj) 메소드를 통해 Secret String여부를 체크하는 것을 확인할 수 있다

m이 무엇인지 살펴보면, CodeCheck이라는 클래스 인스턴스인 것을 확인할 수 있다

 

 

CodeCheck의 클래스를 확인해보면 위의 그림과 같다

가장 먼저, bar() 라는 Native 메소드를 선언한 다음에,
a() 메소드 내부, bar() 메소드에 Parameter값을 Byte로 변환하여 넣는 것을 확인할 수 있다
( 사용자 입력값이 Byte로 변환되어 들어가는 것 )

 

이러한 Native 메소드는 JNI를 통하여 사용할 수 있다 

JNI는 Java에서 Native Code의 메소드를 사용할 수 있게 해주거나,
Native Code에서 Java 메소드를 사용할 수 있게 해주는 프레임워크이다

Java에서 Native 메소드를 사용하기 위해서는 2가지 조건이 필요하다

◆ 위에서 알아보았듯, 먼저 Native로 선언해야한다 
System.load 혹은 System.loadLibrary를 통해 외부 파일을 불러와야 한다

 

MainActivity 클래스에서 확인해보면 바로 위의 그림과 같이, 해당 메소드를 확인할 수 있다
foo 파일을 불러오고 있다

 

위의 그림처럼 jadx를 통하여 바로 확인할 수 있지만,
어떤 아키텍처의 so 파일이 올라가 있는지 모르기 때문에 체크하는 과정이 필요하다

 

체크하는 방법은 위의 그림과 같다

프로세스의 PID를 구한 후, 프로세스 관련 정보가 저장된 /Proc로 이동후, 해당 PID 디렉토리로 이동한다
그리고 메모리 관련 정보가 저장되어 있는 maps 파일에서, 로드된 so 파일을 찾으면 된다

위의 그림처럼 x86 아키텍처의 so 파일이 사용되고 있음을 확인할 수 있다

 

adb pull을 통해 해당 파일을 가져온 후, IDA 디스어셈블러를 통해 해당 파일을 열어본다

JNI를 통해 사용되는 Native 메소드는 "패키지_클래스_메소드" 순으로 이름이 작성되기 때문에
IDA에서 Java_sg_vantagepoint_uncrackable2_CodeCheck_bar 메소드를 열면 위의 그림과 같다
( F5를 누르면 C언어로 변환하여 보여준다 )

 


 

해결 방법 1

 

jadx 디컴파일을 통해 verify() 메소드를 확인해보면 this.m.a(obj)의 값이 참이 나와야한다
그리고 IDA를 통해 bar() 메소드를 확인해보면 1이 나와야만이 this.m.a(obj)가 참이 나온다

결론적으로, bar()메소드가 1을 반환하게 해주면 해당 문제를 해결할 수 있다
Frida를 사용하여, so 파일의 bar() 메소드를 후킹해보겠다

Java.perform(function(){
  console.warn("[+] Frida Start ...");
  let Activity = Java.use("sg.vantagepoint.a.b")
  Activity.a.implementation = function(){return false}
  Activity.b.implementation = function(){return false}
  Activity.c.implementation = function(){return false}

  Interceptor.attach(Module.getExportByName("libfoo.so", "Java_sg_vantagepoint_uncrackable2_CodeCheck_bar"), {
    onEnter : function(arg){},
    onLeave : function(arg){arg.replace(1)}
  })
  console.warn("[+] Done !")
})

코드를 실행하면 위의 그림과 같이, 아무거나 입력하여도 Success가 뜨는것을 확인할 수 있다
( 위에서 발생하는 오류는 코드를 재저장하면 오류없이 실행 할 수 있다 )

 

Java.perform(function(){
  console.warn("[+] Frida Start ...");
  let Activity = Java.use("sg.vantagepoint.a.b")
  Activity.a.implementation = function(){return false}
  Activity.b.implementation = function(){return false}
  Activity.c.implementation = function(){return false}

  let Activity2 = Java.use("sg.vantagepoint.uncrackable2.CodeCheck")
  Activity2.a.implementation = function(arg){return true}
  console.warn("[+] Done !")
})

혹은 Module내부의 함수를 건드리지 않고, CodeCheck 클래스의 a 함수가 true를 리턴하게 변경하여도
해결할 수 있다 위의 코드와 같다

 


 

해결 방법 2

bar() 함수의 코드를 살펴보면 strcpy 함수를 사용하여
길이 24 배열 변수v5에 "Thanks for all the fish" 문자열을 복사하여 넣었다

그리고 무엇인지는 모르겠지만 특정 연산 다음에
strncmp 함수를 사용하여, 변수 v3, v5가 길이 23인지를 비교하고 있다
만약 두 변수의 길이가 같다면 0을 반환하는데 ! 연산자로 인하여 True로 변경된다

앞의 연산들지 어떠한 연산을 하는지는 모르겠으나, Thanks for all the fish와 비교하는것은 확실하다

해당 문자열을 넣어보면 성공하는 것을 확인할 수 있다

 

Java.perform(function(){
  console.warn("[+] Frida Start ...");
  
  let system = Java.use("java.lang.System")
  system.exit.overload("int").implementation = function(){
    console.warn("Avoid exiting the App :)");
  }
  Interceptor.attach(Module.findExportByName("libfoo.so","strncmp"),{
    onEnter: function(args){
        var param1 = Memory.readUtf8String(args[0]);
        var param2 = Memory.readUtf8String(args[1]);
        if(param1.indexOf('01234567890123456789012') !== -1){
            console.log(param1);
            console.log(param2);
        }
    },
    onLeave: function(retval){}
  })
  console.warn("[+] Done !")
})

만약, Secret String을 모른다고 했을 때, 확인하는 코드는 위와 같다

길이 23을 맞춘 "01234567890123456789012" 문자열을 넣었을 때,
strncmp() 함수의 Argument로 들어오는 것을 출력하는 코드이다

◆ onEnter의 args는 후킹한 함수가 실행되기 전의 Argument를 배열로 받고,
◆ onLeave의 retval은 함수의 return값을 받는다
◆ indexOf는 문자열에서 특정 문자열을 찾은 후, 해당 문자열 첫 번째 문자의 index를 반환한다 일치하지
않을 경우는 -1을 반환한다

 

해당 코드를 실행하면 위의 그림과 같이, 결과를 얻을 수 있다

 


 

오류

참고로, 위의 코드를 Android 5 (32bit) 환경에서 그대로 사용하면 위의 그림과 같이 에러가 발생한다
이유는 찾지 못했다

하지만 Android  9 (64bit) 환경에서 사용할 경우, 에러없이 성공적으로 실행되는 것을 확인하였다

 


 

마무리

해당 문제를 풀어보면서, JNI에 대해서 처음 배웠다

풀이 과정 또한 다양하게 찾아보았는데, Ghidra, Radare2 등 자기만의 방식으로 풀어본 사람들이 많았다
해당 단계 덕분에 많은 공부를 하게 된 것 같다

 

끝 ~

반응형
🛸
축하합니다!
포스트를 모두 읽으셨습니다! 👽✨
⏱️ 읽기 시간
계산중...
소요되었습니다
🔒 Security/Mobile

[붉은외계인] Mobile - OWASP UnCrackable L1

🗓️ 2024. 3. 4. 10:44
⏱️ 계산 중...
반응형

Frida 연습을 위하여, OWASP에서 래퍼런스로 제공하는 UnCrackable Level 1을 풀어보았다

OWASP에서는 래퍼런스로 Android, iOS 상당히 많은 앱을 제공하고 있고, 앱 외에도 해킹 도구라던지 
각종 좋은 정보들을 제공하고 있다

아래 링크를 첨부할테니, 꼭 한번씩 들어가보길 바란다

https://mas.owasp.org/MASTG/apps/

 

Reference applications - OWASP Mobile Application Security

Reference applications The applications listed below can be used as training materials. Note: only the MASTG apps and Crackmes are tested and maintained by the MAS project. Android Apps Ios Apps

mas.owasp.org

 


 

설치 및 실행

 

설치를 한 후, 실행을 하면 위와 같이 Rooting 감지를 하며 종료가 된다

참고로, Android 9(64bit) 환경에서는 해당 앱의 Rooting 감지가 안된다
필자는 Android 5에서 실행한 결과, 성공적으로 Rooting 감지를 확인하였다
공부를 위해서는 Android 5 환경에서 설치 및 실행하길 바란다

 


 

 Rooting 우회

가장 먼저, Frida를 사용하여 Rooting을 우회해보겠다  

 

jadx를 통해 확인해보면, 앱이 onCreate 될 때, c.a(), c.b(), c.c()를 실행하는것을 확인할 수 있다

해당 메소드를 확인해보면 환경변수를 가져와 체크하는 a()
빌드 태그를 체크하는 b()
루팅 여부를 확인하는 c()

조건이 충족된다면 True를 반환하면서, MainActivity의 a() 메소드를 실행하면서 앱이 종료된다
루팅을 우회하기 위해서는 위의 3개 메소드를 return false로 만들면 된다

Frida를 사용하여, 해당 코드를 구현하면 아래와 같다

Java.perform(function(){
  console.warn("[+] Frida Start ...");
  
  let Acvtivity = Java.use("sg.vantagepoint.a.c")
  Acvtivity.a.implementation = function(){return false}
  Acvtivity.b.implementation = function(){return false}
  Acvtivity.c.implementation = function(){return false}

  console.warn("[+] Done !");
})

 

코드를 실행하면, 위의 그림과 같이 성공적으로 우회한 것을 확인할 수 있다 


우회에 성공하면 특정 String을 입력하라는 폼이 나오는데, 아무거나 입력해보면 
위의 그림과 같은 경고창이 뜬다 다시 jadx를 통해서 확인해보자

 


 

분석

uncrackable1.MainActivity 클래스

 

사용자가 입력폼에 입력한 텍스트 값을 a.a()메소드의 Argument로 주고있다

 

uncrackable1.a 클래스

a()메소드를 살펴보면 사용자 입력값과 특정 문자열을 다른 패키지 a()메소드의 Argument로 주고있다

 

sg.vantagepoint.a.a 클래스

a() 메소드를 살펴보면 위와 같다

메소드의 동작을 요약하자면
bArr을 Key로 설정하고, AES 알고리즘의 ECB 모드를 사용하여 bArr2 데이터를 해독한다 


해독이 성공하면 해독 문자열을 반환하고, 해당 반환값이 
uncrackable1.a.a() 메소드의 str.equals(new String(bArr));의 bArr로 들어간다


그리고 사용자 입력값인 str과 같은지 비교하여, True/False를 반환한다
여기서 해당 앱의 성공 포인트를 정리하면 아래와 같다

 

◆ Frida를 사용하여 uncrackable1.a.a() 메소드가 True를 반환하게 하기
◆ Frida를 사용하여 sg.vantagepoint.a.a.a()메소드가 해독하여 반환하는 Return값을 찾아내어 직접 사용자 입력폼에 입력하기

 

 

다음으로, 각 포인트에 맞는 코드를 짜보자

 


 

풀이 방법 1

Java.perform(function(){
  console.warn("[+] Frida Start ...");

  let Activity = Java.use("sg.vantagepoint.a.c")
  Activity.a.implementation = function(){return false}
  Activity.b.implementation = function(){return false}
  Activity.c.implementation = function(){return false}

  let Acvtivity = Java.use("sg.vantagepoint.uncrackable1.a")
  Acvtivity.a.implementation = function(){return true}

  console.warn("[+] Done !");
})

 

해당 코드를 실행시키면, 어떤 문자열을 입력하든 위의 그림과 같이 Success가 뜬다
출제자가 의도한 것은 아니겠지만 어쨌든, 위 방법은 경험상 굉장히 유용했으니 알아두면 좋다

 


 

풀이 방법 2

function arrayToStr(arr){
  let result = ""
  for(let i=0; i < arr.length; i++){
    result += String.fromCharCode(arr[i])
  }
  return result
}
setImmediate(function(){
  Java.perform(function(){
    console.warn("[+] Frida Start ...");
    let Activity = Java.use("sg.vantagepoint.a.c")
    Activity.a.implementation = function(){return false}
    Activity.b.implementation = function(){return false}
    Activity.c.implementation = function(){return false}
    

    let Activity2 = Java.use("sg.vantagepoint.a.a")
    Activity2.a.implementation = function(b1,b2){
      console.warn(`Function Parameter : ${b1}, ${b2}`);
      let result = this.a(b1, b2)
      console.warn(`Function Return Value : ${ arrayToStr(result)}`);
      return result
    }

    console.warn("[+] Done !");
  })
})

 

위의 코드를 실행 후, 프롬포트를 살펴보면
sg.vantagepoint.a.a.a()메소드가 해독하여 반환하는 Return값을 알 수 있다


바로 I want to believe이다 

 

입력하면 Success 가 뜨는 것을 확인할 수 있다

 


 

마무리

해당 앱을 해결하는 과정 동안 정말 재미있었다

다음에는 UnCrackable L2를 풀어볼 것이다 :)

끝 ~

반응형
🛸
축하합니다!
포스트를 모두 읽으셨습니다! 👽✨
⏱️ 읽기 시간
계산중...
소요되었습니다
🔒 Security/Mobile

[붉은외계인] Mobile - FridaLab 풀이

🗓️ 2024. 2. 26. 22:19
⏱️ 계산 중...
반응형

들어가며

Frida를 배우고 난 후, 연습을 위하여 FridaLab 을 풀어보았다 

1 ~ 5단계는 쉽기 때문에, 해당 포스트에서는 6 ~ 8 단계만 다루고 있다
나머지 Solution Scripts는 아래 Github에 올려놓았으니, 참고하길 바란다

https://github.com/RedAlien00/FridaLab

 

GitHub - RedAlien00/FridaLab

Contribute to RedAlien00/FridaLab development by creating an account on GitHub.

github.com

 


 

서론

 

FridaLab을 풀어볼 때,
외우는것이 아닌 왜 이러한 메소드를 썼고, 이러한 코드를 짰는가에 포인트를 두고 진행하였다


그리고 많은 풀이들을 살펴보고, 이리저리 바꾸어가며 나의 걸로 만들려고 시도하였다
그 결과, 몇 가지 깨달은점이 있다 

 

◆ JavaScript의 화살표 함수를 사용할 경우, 오류가 발생한다는 것
◆ 조건문의 경우, True로 바꿔버리면 된다는 것 ( 이것은 아래에서 다룰 것이다 )
◆ use와 choose의 차이점을 확실히 이해해야 한다는 것

위의 그림을 살펴보자

Java.perform에서 사용하는 화살표 함수는 대부분의 경우에서 문제 없이 작동하였지만,
그 외에서 사용할 경우, 위와 같이 에러가 발생하는 경우가 많았다 
에러가 발생하는 원인을 모르겠다면, 화살표 함수를 변경해보자

 

 

let Activity = Java.use(""uk.rossmarks.fridalab.MainActivity"")

let Activity = Java.choose("uk.rossmarks.fridalab.MainActivity", {
    onMatch : function(){}
    onComplete : function(){}
  })

개인적으로, 가장 많은 시간을 할애했던 것은 use와 choose의 사용 방식이다

시간이 많이 들었던 이유는, 메뉴얼이 생각보다 자세하지 않았고,
검색을 통해 찾아보아도 각자 말하는 정의들이 달랐기 때문이다
Frida의 메뉴얼과 구글링, 그리고 FridaLab을 여러 번 풀어보면서, 나름대로 정의한 것은 아래와 같다

 

Java.use
- Static 맴버(정적 맴버)에 접근하는 메소드

Java.choose
- Heap을 스캔하여, 타겟 클래스의 인스턴스를 열거하는 메소드
- 메모리에 적재된 인스턴스 맴버에 접근하는 메소드

 

이제부터 본론으로 들어가보자

 


 

Challenge 6

해당 단계의 목표는 올바른 Value를 통해, 10초 뒤에 chall06을 실행하는 것이다
코드를 살펴보자

// Solution 1
Java.perform(function(){
  console.log("[+] Frida Start ...");
  let Main_Act = Java.choose("uk.rossmarks.fridalab.MainActivity",{
    onMatch : function(arg){
      let chall06_Act = Java.use("uk.rossmarks.fridalab.challenge_06")
      chall06_Act.confirmChall06.implementation = function(){return true}
      arg.chall06(1);
    },
    onComplete : function(){console.warn("Done !");}
  })
})

위에 코드는 올바른 Value를 삽입하는 것이 아닌,
chall06_Act_confirmChall06 메소드가, true를 반환하도록 재구현하여, 해결하는 방법이다

내가 위에서 언급한 " 조건문의 경우, True로 바꿔버리면 된다는 것 " 은 위와 같은 케이스를 의미한다
앱의 코드를 살펴보자

 // MainActivity
 public class MainActivity extends AppCompatActivity {
     public void chall06(int i) {
            if (challenge_06.confirmChall06(i)) {
                this.completeArr[5] = 1;
            }
        }
    }
// challenge_06
public static boolean confirmChall06(int i) {
        return i == chall06 && System.currentTimeMillis() > timeStart + 10000;
    }

결론적으로, chall06() 메소드를 실행하였을 때 True가 된다면 해결된다
그렇기 때문에, chall06_Act_confirmChall06 메소드가, True를 반환하도록 재구현한 것이다

제작자가 원하는 방식과는 다르지만, 어쨌든 하나의 풀이 방법이 될 수 있다
해당 코드는 challenge 3단계의 풀이 방법과 비슷하며, 조금 더 응용했다고 보면 된다

 

 

// Solution 2
Java.perform( console.warn("[+] Frida Start ..."))

setTimeout(function(){
  Java.perform(function(){
    Java.choose("uk.rossmarks.fridalab.MainActivity",{
      onMatch : function(arg){
        let Activity = Java.use("uk.rossmarks.fridalab.challenge_06")
        const value = Activity.chall06.value
        arg.chall06(value)
      },
      onComplete : function(){}
    })
    console.warn("[+] chall06 Success ! !");
  })
}
, 10000);

반대로, 위에 코드는 제작자가 원하는 방식의 풀이 방법을 기술한 것이며, 해결 포인트는 아래와 같다

 

1. MainActivity가 Oncreate 될 때, 위의 그림처럼 메소드가 호출되면서 challenge_06 클래스의
chall06, timeStart 필드값이 설정된다는 것
2. 10초가 지난 뒤에, chall06값과 일치하는 int 자료형을 넣으면 해결된다는 것

 

10초가 지난 뒤에 실행은 setTime() 함수로 구현하면 된다

문제 해결을 위해서는 MainActivity.chall06() 메소드를 호출하여야 하는데, 해당 메소드는
인스턴스 맴버이기 때문에, Java.use가 아닌, Java.choose를 사용해야 한다
( 위에서 언급하였지만, Java.choose는 메모리에 적재된 인스턴스 맴버에 접근하는 메소드라고 하였다 )

 

하지만 challenge_06의 chall06 필드는 static 맴버이기 때문에 Java.use를 사용하여 접근하여야 한다


 

Challenge 7

이번 단계의 목표는 check07pin()메소드를 Brute Force하여 chall07을 confirm 하는 것이다

// Solution 1 
Java.perform(function(){
  console.log("[+] Frida Start ...");
  Java.choose("uk.rossmarks.fridalab.MainActivity",{
    onMatch: function(arg){
      let Act_chall07 = Java.use("uk.rossmarks.fridalab.challenge_07")
      Act_chall07.check07Pin.implementation = function(){return true}
      arg.chall07('A')
    },
    onComplete : function(arg){console.log("[+] Done !");}
  })
})

위에 코드는 6단계 과정과 같은 원리로, true를 반환하도록 재구현 한 것이다 
같은 원리이기 때문에 설명은 생략하겠다

 

// Solution 2
Java.perform(function(){
  console.warn("[+] Frida Start ...")
  Java.choose("uk.rossmarks.fridalab.MainActivity",{
    onMatch: function(arg){
      const chall07 = Java.use("uk.rossmarks.fridalab.challenge_07")
      for(let i=1000; i<9999+1; i++){
        if(chall07.check07Pin(String(i))){
          console.warn(`Found ! ${i}`);
          arg.chall07(String(i))
          break
        }
        console.warn(`Brute Force : ${i}`);
      }
    },
    onComplete : function(arg){}
  })
  console.warn("[+] chall07 Success ! !");
})

위에 코드는 제작자가 원하는 방식의 해결 방법을 구현한것이다

 

이것 또한 위에서 설명한 것과 마찬가지로, 인스턴스 멤버는 Java.choose를, 
Static 맴버는 Java.use를 사용하여 접근하면 된다

0.0 ~ 0.9 의 난수를 생성하는 Math.random()함수 뒤에 추가로 연산이 있는데,
이는 결국 1000 ~ 9999이하 범위의 난수를 생성하는 것이다

 

그리고 랜덤 숫자는 "" + <랜덤숫자> 의 연산을 통해 chall07 필드에 저장된다
빈 문자열과 문자열 연결 연산이 되기 때문에, 만약 랜덤 숫자가 8이라면 "8" 문자열이 저장될 것이다
이러한 이유로, chall07() 메소드에 매개변수를 집어넣을 때는 문자열로 자료형 변환을 해주어야 한다

 


 

challenge 8

해당 단계에서 목표는 check 버튼의 text value를 Confirm으로 변경하는 것이다

// Solution 1
Java.perform(function(){
  const Activity = Java.use("uk.rossmarks.fridalab.MainActivity")
  Java.choose("uk.rossmarks.fridalab.MainActivity", {
    onMatch: function(arg){
      Activity.chall08.implementation = function(){return true}
      arg.chall08()
    },
    onComplete : function(){}

  })
})
// Solution 2
Java.perform(function(){
  console.warn("[-] Frida Start ...");

  Java.choose("uk.rossmarks.fridalab.MainActivity", {
    onMatch: function(arg){
      const checkId = arg.findViewById(0x7f07002f)      // View 객체 리턴
      const button = Java.use("android.widget.Button")
      const check = Java.cast(checkId, button)          // Button으로 타입 변환
      const string = Java.use("java.lang.String")
      check.setText(string.$new("Confirm"))            // String 객체 Confirm 생성
    },
    onComplete : function(){console.warn("[+] chall08 Success ! !");}
  })
})

Solution 1은 생략하고, 바로 2를 설명하겠다 

여기서, Java.cast는 형 변환 or 타입 변환을 해주는 메소드인데, Java 코드로 예를들면 아래 코드와 같다

Button button = (Button) findViewById(R.id.mybutton);

findViewById 메소드에 Resourece Id를 매개변수로 주면 해당 ID에 해당하는 View 객체가 리턴된다
그것을 Button으로 형 변환을 하는 코드이다

다음으로, $new 키워드는 객체의 생성자를 호출하여 인스턴스화 시키는 키워드이다
setText() 메소드에 바로 Confirm 을 삽입하면, 에러가 발생하기 때문에 $new 키워드를 통해
String 객체를 생성한 후, 삽입해주어야 한다


 

마무리

 

완전히 이해하는데까지 생각외로 시간이 꽤 걸린 것 같았다

위에서 언급하였듯, Java.use와 Java.choose가 시간을 많이 잡아먹었는데 
대충하고 넘어가면 나중에 발목을 잡을 것 같아서 끝까지 파고드느라 그렇다

 

 아! 그리고 아래 사이트에서도 많은 공부를 할 수 있었으니, 해당 게시물을 접하는 분들도
한번씩 꼭 들러보면 좋겠다

https://learnfrida.info/

 

Frida HandBook

 

learnfrida.info

 

끝 ~

반응형
🛸
축하합니다!
포스트를 모두 읽으셨습니다! 👽✨
⏱️ 읽기 시간
계산중...
소요되었습니다
🔒 Security/Mobile

[붉은외계인] Mobile - smali 코드 분석 1 with KGB Messenger

🗓️ 2024. 2. 14. 17:58
⏱️ 계산 중...
반응형

KGB Messenger CTF를 하던 중에, 처음으로 smali 코드 분석을 하게 되었다

smali코드를 이해할 수 있다면 추후, 취약점 분석에 많은 도움이 될 거 같아서,
오늘 공부한 내용을 정리하려한다
시작해보자

 


 

Java 소스 코드

아래는 원본 Java 소스 코드이다 ( 디컴파일은 jadx-gui를 통해 하였다 )

package com.tlamb96.kgbmessenger;

import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.p022v7.app.ActivityC0494c;
import android.support.p022v7.app.DialogInterfaceC0492b;
import android.widget.LinearLayout;
import com.tlamb96.spetsnazmessenger.R;
import p000a.p001a.p002a.p003a.C0000a;

/* loaded from: classes.dex */
public class MainActivity extends ActivityC0494c {
    /* renamed from: a */
    private void m531a(String str, String str2) {
        DialogInterfaceC0492b m2324b = new DialogInterfaceC0492b.C0493a(this).m2324b();
        m2324b.setTitle(str);
        m2324b.m2331a(str2);
        m2324b.setCancelable(false);
        m2324b.m2333a(-3, "EXIT", new DialogInterface.OnClickListener() { // from class: com.tlamb96.kgbmessenger.MainActivity.1
            @Override // android.content.DialogInterface.OnClickListener
            public void onClick(DialogInterface dialogInterface, int i) {
                dialogInterface.dismiss();
                MainActivity.this.finish();
            }
        });
        m2324b.show();
        LinearLayout linearLayout = (LinearLayout) m2324b.m2334a(-3).getParent();
        linearLayout.setGravity(1);
        linearLayout.getChildAt(1).setVisibility(8);
    }

    @Override // android.support.p008v4.p010b.ActivityC0099l, android.app.Activity
    public void onBackPressed() {
        Intent intent = new Intent("android.intent.action.MAIN");
        intent.addCategory("android.intent.category.HOME");
        intent.setFlags(268435456);
        startActivity(intent);
    }

    @Override // android.support.p022v7.app.ActivityC0494c, android.support.p008v4.p010b.ActivityC0099l, android.support.p008v4.p010b.AbstractActivityC0090h, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        String property = System.getProperty("user.home");
        String str = System.getenv("USER");
        if (property == null || property.isEmpty() || !property.equals("Russia")) {
            m531a("Integrity Error", "This app can only run on Russian devices.");
        } else if (str == null || str.isEmpty() || !str.equals(getResources().getString(R.string.User))) {
            m531a("Integrity Error", "Must be on the user whitelist.");
        } else {
            C0000a.m3944a(this);
            startActivity(new Intent(this, LoginActivity.class));
        }
    }
}

 


 

smali 코드 1

smali코드를 부분부분 보면서, 해석을 하겠다

그 전에, 미리 알아둬야 할 것을 적어두겠다

◆ p0, p1, p2 ~ : p0은 보통 java에서와 같이 this를 의미한다 p1, p2 ~ 는 레지스터를 의미하되,
     매개변수로 들어가는 것을 의미한다
◆ v0, v1, v2 ~ : 이것 또한 레지스터를 의미하되, 전역 변수를 담는 레지스터이다

1번째 줄 : 현재 클래스의 경로를 나타낸다
2번째 줄 : 부모 클래스의 경로를 나타낸다

6번째 줄 : 생성자를 정의하는 부분이다 <init>()은 어떠한 매개변수도 받지 않는다는 의미

Java에서는 객체를 생성할 때, 반드시 생성자를 호출하게 된다 
하지만 클래스를 정의할 때, 생성자를 정의하지 않았더라도, 컴파일러가 자동으로 생성자를 생성한다
원본 소스 코드를 보면, 생성자를 정의하는 부분이 없다 그래서 이러한 smali 코드가 생성된 것 같다

7번쨰 줄 : 해당 메소드에서 사용되는 레지스터는 1개이다

9번째 줄 : invoke-direct는 메소드를 직접 호출하는 명령어이다
invoke-direct의 특징은 public,protected의 상관없이 메소드를 호출할 수 있다

11번째 줄: return-void는 아무것도 반환하지 않고 return(종료) 한다는 의미이다
12번째 줄: 메소드 정의 종료


 

smali 코드 2

1번째 줄: 2개의 String 자료형을 매개 변수로 받는 메소드 a 정의
2번째 줄: 해당 메소드는 8개의 레지스터를 사용한다
4번째 줄: 레지스터 v4에 4비트 상수 1을 저장 ( const/4는 4bit를 의미 )
6번째 줄: 레지스터 v3에 4비트 상수 -3을 저장
8번째 줄: 레지스터 v0에 객체 할당
10번째 줄: v0과 p0을 매개변수로 받는 생성자 호출 ( 여기서 p0은 this와 같은 의미 )
12번째 줄: v0을 매개변수로 받는 가상 메소드 b를 호출한다 해당 메소드의 반환 타입은 Landroid/support/v7/app/b 
16번째 줄: v0과 p1을 매개변수로 받는, setTitle 매소드를 호출한다 여기서 p1은 Ljava/lang/CharSequence를 의미한다
또한 반환 타입은 아무것도 반환하지 않는 V(void)이다
18번째 줄: v0과 p2를 매개변수로 받는 a 메소드를 호출한다 여기서 p2는 Ljava/lang/Charsequence를 의미한다 
이것 또한 반환 타입은 V이다
20번째 줄: 생략
22번째 줄: 생략 ( 여기서 Z는 Boolean 자료형을 의미한다 )

 


 

smali 코드 3

1번째 줄: v1 레지스터에 문자열 "EXIT" 할당
3번째 줄: MainActivity의 내부 클래스 객체 생성
5번째 줄: v2와 p0(MainActivity)를 매개변수로 받는 생성자 호출
8번째 줄: android/support/v7/app/b에 속한 a 메소드 호출, CharSequence와 OnClickListener를 매개변수로 받으며, 반환하는게 없는 V 
11번째 줄: v0을 매개변수로 받는 가상메소드 show()  호출 반환 타입 V
13번째 줄: v0과 v3를 매개변수로 받는 android/support/v7/app/b에 속한 a 메소드 호출, I(Integer)를 매개변수로 으며, Landroid/widget/button을 반환한다
15번째 줄: 이전에 호출된 반환값을 v0 레지스터에 저장한다
--------- 생략 -----------
33번째 줄: return-void는 아무것도 반환하지 않는다는 뜻

 


 

smali 코드 4 ( 이번건 길기 때문에, 중복된 내용은 과감히 건너뛴다 )

4번째 줄: invoke-super는 부모 클래스의 메소드를 호출하는 명령어로서, 여기서는 onCreate을 호출한다
6번째 줄: 레지스터 v0에 상수 0x7f09001c를 할당하고 있다 이것은 리소스 ID를 의미한다
12번째 줄: invoke-static은 정적 메소드를 호출하는 명령어로서, getProperty를 호출하고 있다
22번째 줄: if-eqz는 equal to zero의 약자로, 0일 때 실행하는 조건문이다 여기서는 v0이 0일 때, cond_25 레이블로 이동하라는 의미이다

28번째 줄: if-nez는 not equal to zero의 약자로, 0이 아닐때 실행하는 조건문이다 여기서는 v2가 0이 아닐 때, con_25 레이블로 이동하라는 의미이다
38 ~ 46번째 줄: cond_25 레이블을 의미한다

 


  

마무리

오늘 알아본 소스코드의 smali 코드는 하나씩, 천천히 알아보면서 읽어나가다 보면 어렵지 않다
여담으로, 나는 다른 툴도 써보았지만 리버싱할 때, jadx-gui와 apktool-GUI를 자주 사용한다

 jadx-GUI의 경우,  업데이트도 꾸준히 하고 있고 무엇보다 smali 코드와 
소스 코드를 같이 볼 수 있다는 점, 그리고 검색 기능이 가장 강력하다고 생각한다

apktool-GUI의 경우, CLI 기반인 apktool을 사용하다 불편하여, 찾아보다가 apktool-GUI를 택하였다
이것 또한 업데이트가 꾸준한데, 무려 3주전에 업데이트가 됐을 정도로 따끈따끈하다
또한, 디컴파일한 apk파일의 코드를 수정하여 다시 컴파일 할 때, 서명 과정도 알아서 해준다
기존에는, uber-apk-signer를 통해 서명을 해줘야 했지만, apktool-GUI는 그러한 번거로움을 없애준다

끝 !@

반응형
🛸
축하합니다!
포스트를 모두 읽으셨습니다! 👽✨
⏱️ 읽기 시간
계산중...
소요되었습니다
🔒 Security/Mobile

[붉은외계인] Mobile - Diva Hardcoding Issues - Part 2

🗓️ 2024. 2. 7. 12:10
⏱️ 계산 중...
반응형

 

Nox 에뮬레이터에, 취약한 모바일 앱인 diva를 통하여 공부를 하던 도중 상당히 재미있었던 부분을 정리할려고 한다
시작해보자

 


 

Hardcoding Issues - Part 2

 

Part 1은 정말 쉬웠으나, Part 2는 jadx를 통해 디컴파일한 소스를 들여다보는 과정 + 추가 작업이 필요하다

Hardcode2Activity 클래스에서는 DivaJni 클래스의 access 함수를 사용한다
이러한 access 함수는 native 키워드가 사용되었기 때문에, JNI를 통해서 라이브러리를 호출 할 때 구현될 것이다

어떤 라이브러리를 호출하는지 살펴보면 divajni 라이브러리를 호출하고 있다

 


 

라이브러리

jadx를 통해 컴파일 한 후, lib 디렉토리를 살펴보면
각 아키텍쳐마다 사용할 수 있게끔, 동적 라이브러리가 정의되어 있음을 알 수 있다

그렇다면 Nox 에뮬레이터가 구현한 OS는 어떤 아키텍쳐를 사용하고 있고,
이로 인해 어떠한 라이브러리를 사용하고 있는지를 확인하여야 할 것이다 

확인방법으로는 동적 라이브러리는 메모리에 로드 되니, 해당 프로세스의 메모리를 확인하면 될 것이다

 

ps를 통해 diva의 PID를 알아 낸 후, 시스템/프로세스의 정보가 저장되는 proc의 디렉토리로 이동한다
그 다음, diva PID에 해당하는 디렉토리로 이동을한다

그리고 현재 프로세스가 사용하고 있는 메모리 영역에 대한 정보를 저장하는 maps 파일을 열람한다
이 때, 많은 내용이 나오기 때문에, 라이브러리 이름으로 필터를 해준다

그 결과, x86_64 디렉토리의 libdivajni.so 사용을 확인할 수 있다

 


 

해결 방법 1 - 무작정 대입

이번 쳅터의 목적은 하드코딩 코드를 찾아서, 특정 문자열을 입력하는 것이다
구글 검색을 통하여 해결 과정을 찾아보았는데, 상당히 많은 사람들이 각자만의 방식으로 풀어나가서,
보는 재미도 쏠쏠하였다

나는 그 중에서, 2가지 방법으로 해결해보고자 한다

 

첫 번째는 무작정 대입이다
무식한 방법이기도 하지만 strings 유틸을 이용하여, so 파일 중 ASCII 문자만 뽑은 후, 전부 대입해 보는 것이다

단순함이 복잡함을 이긴다는 말이 떠오르게 하는 방식이다

 


 

해결 방법 2 - dump

 

objdump 유틸을 이용하여, so 파일을 덤프해 보는 것이다

ELF 포멧에서, 상수/문자열이 저장되는 섹션인 .rodata 섹션을 덤프해본 결과,
위와 같은 문자열이 저장되어 있음을 확인하였다

 


 

결론

 

olsdfgad;lh 를 입력하면 성공 ~

반응형

붉은외계인의 성장 기록

코드부터 일상까지, 배움과 경험을 공유합니다

🔍
스크롤해서 더 보기

함께하는 구독자들

구독자 8명

최신 포스트