일단 쓰고보자

Uncrackable-Level2 문제 풀이(frida Hooking) 본문

모의해킹/Uncrackable

Uncrackable-Level2 문제 풀이(frida Hooking)

휘갈기갈기 2020. 4. 1. 17:43

#서론

 

Uncrackable-Level1에 이어 Level2도 frida Hooking을 통해 풀어보았다.

 

문제를 풀면서 가장 어려웠던 부분은 라이브러리 파일 분석하는 것과, 역시나 Hooking 코드 작성하는 부분이었다. 

 

일반 함수, 네이티브 함수 Hooking 까지 배웠으니 Level3에선 어떤 문제가 나올지 궁금해진다(사실 안 궁금).

 

난이도는 매우 높지만, 그만큼 강력한 방법이라 앞으로 frida를 통한 Hooking 연습을 더 해볼 예정이다.

 


#준비물

1. Nox

2. ADB

3. Python

4. Hooking Code

5. Uncrackable-Level2.apk

6. IDA(★)

7. JEB

 


#시작 전 참고사항

 

frida Hooking은 심플하다.

1) 스마트폰에서 frida-server 실행

2) Python으로 Hooking 코드 실행

 

frida-server 등 준비물들 설치하는 방법은 이전 글 참고바란다.

https://kinghy7.tistory.com/1?category=791142

 

Nox 에서 fridump3 로 메모리 덤프하기

#서론 이 간단한 걸 하기 위해 얼마나 삽질을 했는가 #목차 1. Nox 설치 2. Python 설치 3. frida-server 설치 4. fridump 설치 5. frida 설치 6. 대상 앱 설치 및 메모리 덤프 #시작 1. Nox 설치 - 메인 페이지에..

kinghy7.tistory.com

추가로 Frida Hooking 통해 Uncrackable-1 문제 풀었을 때와 방식은 거의 흡사하다. Hooking 코드만 변경되었을 뿐.

그러니 더 궁금한 것이 있다면 해당 풀이를 참고해보자.

https://kinghy7.tistory.com/7

 

Uncrackable-Level1 문제 풀이(frida Hooking)

#서론 이전 게시글에 Uncrackable-Level1.apk 풀이를 업로드 했었다. apk 파일 Decompile 한 후 smail 파일 직접 수정 및 JEB 디버깅을 통해 풀어보았는데 이번엔 frida를 사용하여 Hooking code 작성함으로써 문..

kinghy7.tistory.com

 

Uncrackable-Level2.apk 다운로드 링크

https://github.com/OWASP/owasp-mstg/tree/master/Crackmes/Android

 

OWASP/owasp-mstg

The Mobile Security Testing Guide (MSTG) is a comprehensive manual for mobile app security development, testing and reverse engineering. - OWASP/owasp-mstg

github.com


#시작

 

1. JEB로 소스 전체적으로 둘러보자.

 

# sg.vantagepoint.uncrackable2.MainActivity 클래스

1-1) 기존 Uncrackable-Level1 에 존재하던 루팅 탐지, 디버깅 탐지는 그대로 있다. (우회 필요)

 

1-2) 외부 라이브러리를 사용한다

- 아래 코드를 보면 libfoo.so 이름의 외부 라이브러리를 참조할 것이라는 것을 알 수 있다.

   ※ 코드로 작성할 땐 lib 제외한 이름을 써준다 ("foo")

 

1-3) AsyncTask 클래스를 사용한다

- 백그라운드에서 동작하는 형태의 클래스이고, doInBackground 함수 안에 백그라운드 처리할 동작을 작성한다.

- onPostExecute 는 doInBackground 함수 부분이 모두 처리된 뒤 결과값을 받아 UI에 처리할 함수.

- 이 앱에서 AsyncTask 클래스를 불러서 하고있는 짓은, 사용자가 백그라운드에서 디버깅 중인지 확인하겠다는 얘기.

- 디버깅 중이면 디버깅 그만할 때까지 재운다...

- 그러니 아래 보이는 while 문으로 들어가지 않도록 해야한다.

 

1-4) 실제 입력 값을 검증하는 verify 함수를 살펴본다.

- this.m.a(v4) 값이 참인 경우 저 희망찬 문구를 볼 수 있겠다. this.m.a() 함수는 아래에서 확인해보자.

 

# sg.vantagepoint.uncrackable2.CodeCheck 클래스

1-5) 위 1-4에서 부른 this.m.a() 함수가 아래에 보이는 a 함수이고, 이 함수는 native 함수인 bar()를 호출한다.

- 이 bar()라는 놈이 libfoo.so 에 선언되어 있음을 유추해볼 수 있다. 거기서 Secret Key를 얻을 수 있을 것 같다.

 

1-6) IDA 디어셈블러로 libfoo.so 파일을 까보도록 하자.

- 그 전에 Uncrackable-Level2.apk 디컴파일해서 라이브러리 파일이 위치한 폴더에 들어가보면, 아래와 같이 4개의 폴더가 나오고 어떤 파일을 열어봐야 하는지 당황할 수 있다(각 폴더 동일하게 libfoo.so 파일이 들어있음). 이 땐, 본인이 앱 후킹을 시도할 안드로이드 OS에 해당하는 파일만 후킹하면 된다.

- 확인 명령어

  1) C:\> adb shell getprop ro.product.cpu.abi

  2) # getprop ro.product.cpu.abi

- 내 안드로이드 OS에 맞는 libfoo.so 파일을 열어보자. (x86)

많은 탭 중 Exports 탭을 클릭해보면 아까 찾았던 CodeCheck 클래스 내 bar 함수가 선언되어 있다.

- 아마 처음엔 아래처럼 그래프로 나올텐데, 나중에 코드 흐름 파악할 때 필요할 지도 모르지만 지금은 필요 없다.

F5 버튼이나 TAB 버튼을 눌러서 코드 화면으로 넘어가자.

- 아래 코드를 잘 보면 strncmp 함수로 어떤 문자열을 비교한 후 결과를 처리하게 된다.

- 우선 if문이 참이면  goto LABEL_9; 이동하는 부분을 피해서 무사히 strncmp 함수 호출 부분으로 가야한다.

1) 먼저 피해야할 if문은 byte_4008 값이 1이 아닌 경우

byte_4008 값은 Java_sg_vantagepoint_uncrackable2_Mainactivity_init() 에서 값을 1로 초기화 해주고 있는데, Mainactivity_init()을 거치지 않고 구동되는 경우를 걸러내려고 하는 것 같다.

2) 그 다음 피해야할 if 문은 입력 값이 23이 아닌 경우

 

- 사용자 입력 값과 SecretKey 값을 비교하는 부분일 것으로 판단되며, strncmp 함수의 정의는 아래와 같다.

※ 정의 및 반환 값

#include <string.h>
int strncmp(const char *string1, const char *string2, size_t count);

// Return 값
// string1 < string2 인 경우 0보다 작은 수
// string1 = string2 인 경우 0
// string1 > string2 인 경우 0보다 큰 수

 

- strncmp 함수로 들어오면 아래와 같이 심플하게 생겼다.

내가 해야할 일은 전달받아 비교하는 string1, string2 값을 보는 것. 하나는 내 입력 값, 나머지는 SecretKey일 것이다.

위 코드에서 보면 내부에서 정의된 s2 ~ v11 변수와 관련있는 두 번째 인자가 SecretKey일 확률이 높다.

 

2. Hooking Code 목표

 

소스 코드 분석한 것을 다시 정리해보면,

2-1) 기존 Uncrackable-Level1 에 존재하던 루팅 탐지, 디버깅 탐지 우회

- java.lang.System 클래스에 속해있는 exit() 함수 Hooking (Uncrackable-Level1 동일)

 

2-2) 백그라운드에서 돌아가는 Debugging 탐지 우회 (AsyncTask 내 while 문으로 들어가지 않도록)

- android.os.Debug 클래스에 속해있는 isDebuggerConnected() 함수 Hooking

 

2-3) SecretKey 값 알아내기

- frida의 Interceptor.attach 함수를 이용해서 libfoo.so 파일 내 strncmp 함수 후킹 및 비교하는 string 값 확인(★)

- Interceptor.attach 함수의 기본 형태

Interceptor.attach(target, callbacks[, data])
	

//target에는 Module.getExportByName("[라이브러리 이름]", "[라이브러리 내 함수 이름]")을 작성
//callbacks에는 아래 중 하나 혹은 모두를 인자로 사용 가능
//  1) onEnter(args) - 함수가 받는 인자의 배열인 args 조작
//                     중요한 점은 안드로이드인 경우 args[0]부터 인자 값이라는 것이다.
//                     ※ iOS의 경우엔 args[0] = self, args[1] = selector, args[2-n] = arguments
//  2) onLeave(retval) - 함수의 단일 리턴 값 조작

// 공식홈페이지 링크 (https://frida.re/docs/javascript-api/#interceptor)

 

2-4) 위 Hooking Code 목표를 기준으로 코드를 작성해보면 아래와 같다.

 

더보기
import frida, sys

def on_message(message, data):
	if message['type'] == 'send':
		print("[*] {0}".format(message['payload']))
	else:
		print(message)

PACKAGE_NAME = "owasp.mstg.uncrackable2"

jscode = """
    console.log("[*] Start Hooking");
    Java.perform(function() {
        //for Rooting, Debugging Check (Level1)
        var bypassExit = Java.use("java.lang.System");
        bypassExit.exit.implementation = function (){
            console.log("[*] System.exit() is called");
        }
        //for Debugging Check
        var bypassDebugging = Java.use("android.os.Debug");
        bypassDebugging.isDebuggerConnected.implementation = function (){
            console.log("[*] isDebuggerConnected() is called");
            return true;       
        }
        //for Hooking strncmp in libfoo.so
        Interceptor.attach(Module.getExportByName("libfoo.so","strncmp"), {
            onEnter: function (args){
                var param1 = args[0];
                var param2 = args[1];
                var param3 = args[2];
                
                //for avoiding chaos
                if(param3 == 23){
                    console.log("[*] Your Input = " + Memory.readCString(param1));
                    console.log("[!] Secret Code = " + Memory.readCString(param2));
                }
            }
        })
    });
"""
try:
    device = frida.get_usb_device(timeout=10)
    pid = device.spawn([PACKAGE_NAME]) 
    print("App is starting ... pid : {}".format(pid))
    process = device.attach(pid)
    device.resume(pid)
    script = process.create_script(jscode)
    script.on('message',on_message)
    print('[*] Running Frida')
    script.load()
    sys.stdin.read()
except Exception as e:
    print(e)

 

3. 결과 확인

 

3-1) 안드로이드에서 frida-server 구동 시키고 Hooking 코드 작성한 것을 실행시키자.

 

3-2) 정답 확인

유의할 점은, 난 입력 1번만 했어도 출력이 꽤 많이 되어있는 것을 확인할 수 있다는 것인데, strncmp 함수가 SecretKey 값을 비교할 때만 쓰는게 아니기 때문이다. 사실 코드 전문에서 확인할 수 있 듯, 비교 size 값이 23인 경우에만 명령창에 출력하도록 했는데도 이정도인 것이다. size 값 제한 없이 출력 걸어두면 혼란하기 그지없는 명령창을 보고있어야 한다.

 

 


#오류

- IDA 로 libfoo.so 파일 까봤을 때, 모든 코드의 의미를 이해하지 못했고 strncmp 함수 후킹 잡는 것 부터가 에러 투성이.

- s2 부터 v12 까지 변수 선언 및 자료형 크기에 의한 ESP 주소 부분

  ※ C/C++ 자료형 크기

  1) signed int : 4 bytes

  2) unsigned int : 4 bytes

  3) __int16 : 2bytes

  4) __int32 : 4 bytes

  5) __int64 : 8 bytes

- jscode 작성하는 부분에서 함수 리턴 값, 함수 출력할 때 변환형, Interceptor.attach 사용하는 법 등 온갖 에러가 날 괴롭혔다. 하나하나 나열하기 어렵지만 직접 하다보면 분통 터진다.

 

- super() 에 꽂혀서 super 다 따라가서 정리해보고 그랬는데, 이 부분 1도 안건드려도 되는 부분이었다.

 

- Hooking 하다가 막혀서 IDA 동적 디버깅으로 풀어보려고 했었다. 앱 프로세스가 2개 뜨며 정상적으로 디버깅을 못했는데, 아마 AsyncTask 클래스가 백그라운드로 일을 처리하니 프로세스가 2개 떴던 것 같고, 심지어 디버깅을 방지하고 있던 놈이라 안 되지 않았나 싶다. 나중에 IDA 동적 디버깅을 통해서도 풀어보면 좋을 것 같다.

 

- 다음은 Uncrackable-Level3.apk 도전

 


 

'모의해킹 > Uncrackable' 카테고리의 다른 글

Uncrackable-Level1 문제 풀이(frida Hooking)  (0) 2020.03.25
Uncrackable-Level1 문제 풀이  (0) 2020.02.10