테스트 환경 : 갤럭시 S6 Lite (android 11 : P610XXU2CUC3 (2021.03) 펌웨어)

android 7에서 통했던 /system/lib 폴더에 so를 복사하는 방법이 더이상 안됨

아래와 같은 에러 발생함.

dlopen failed: library "/system/lib64/libinject.so" needed or dlopened by "(unknown)" is not accessible for the namespace "classloader-namespace"

심지어 기존에 있던 시스템 라이브러리를 인젝션해도 동일 에러 발생.

해결 방법은 /data/user/0/app name/files/ 에 so를 복사함.

넷플릭스,정부24앱에서 인젝션 테스트 완료

1. 옵션->빌드 버전에서 현재버전 확인 후 각 버전에 맞는 펌웨어 다운로드

(11버전의 2021.03 펌웨어 P610XXU2CUC3 에서 루팅 확인)

samfw.com/firmware/SM-P610/KOO

https://samfrew.com/model/SM-P610/

2. adb 디버그 허용, oem 언락 허용(빌드버전 연타하면 나옴)

3. 파워 off 후 볼륨 상 + 볼륨 하(One UI 3.0이상,보통 안드로이드11버전)를 동시에 누르면서, 1초후에 usb를 연결한다. (연결되어있는 상태면 안되고 타이밍 맞게 연결해줘야됨) 잠시 후 다운로드 모드로 진입하게 된다.

볼륨상을 길게 눌러 부트로더 언락을 진행한다.

4. 초기화 완료되면 다시 빌드버전 연타한 후 oem이 언락되어 있는지 확인

5. ap.tar에서 boot.img.lz4만 추출후 압축해제 후 magisk manager설치 후 boot.img만 패치

그 후 아래의 명령어로 다시 압축 

c:/> lz4 -B6 --content-size boot.img boot.img.lz4

boot.img.lz4를 원본 ap의 boot.img.lz4에 덮어쓰고 다시 tar로 압축

 

lz4_win32_v1_9_3.zip
0.32MB

6. 파워 off후 다시 다운로드 모드에 진입한다. odin 실행 후 패치한 ap선택 후 tar파일을 선택하여 start를 누른다.

bl, csc(home_csc지정) 도 같이 설정해줘야 한다.

반드시 oem lock이 off 상태인지 확인한다. on이면 2부터 다시 반복한다.

Odin3-v3.14.4.zip
4.20MB

7. odin이 성공하면 몇번 재부팅되면서 실행되는데, 만약 try again, reset 하는 화면이 뜨면 reset한다.

이 과정을 거치고 화면이 보여진다면 다시 태블릿을 종료하고, 파워+볼륨상을 길게 눌러 리커버리 모드로 진입한 후에

wipe data/factory reset  과 wipte cache partition을 실행한다.

(안하면 내장 메모리 용량이 16기가로 인식됨)

아래 항목은 실패 시 참조

만약 에러가 발생하면 파워 + 볼륨하 버튼을 누르고 있는다.

태블릿 초록색 화면에 "An error has occurred while updating the device software. Use the Emergency recovery function in the Smart Switch PC software." 에러가 발생하면 아래의 프로그램을 설치한다.

Frp Hijacke rV1.0 setup.rar
1.12MB

위처럼 입력후 HIJACK IT!을 누르면 재부팅이 된다.

태블릿이 공장 초기화 상태로 되는데, 더이상 oem잠금해제가 보이지 않게 된다. 설정->일반 에서 "날짜 및 시간 자동 설정"을 비활성화 후 날짜를 현재 날짜에서 1달전 쯤으로 설정한다. 그다음 소프트웨어 업데이트를 시도하면 oem 잠금해제가 보이게 된다.

만약 다른 에러로 무한 다운로드 모드 상태가 지속된다면, 패치하지 않은 순정 ap, bl, home_csc펌웨어를 odin으로 올린다.

 

레퍼런스

forum.xda-developers.com/t/sm-p610-flash-only-official-released-binaries-are-allowed-to-be-flashed-recovery.4214009/

forum.xda-developers.com/t/howto-unlock-and-root-the-galaxy-tab-s6-lite.4132289/

 

1. Android Studio -> Create New Project -> Native C++

2. app/build.gradle을 열어보면 아래와 같이 되어있다.

android {
    compileSdkVersion 29
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.codetronik.myapplication"
        minSdkVersion 24
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }

arm64만 빌드하기 위해 externalNativeBuild { } 밑줄에 ndk { }를 추가한다. (수정하지 않으면 x86, x86_64, armeabi까지 모두 빌드됨)

        ndk {
            abiFilters 'arm64-v8a'
        }

만약 armeabi-v7a도 추가한다면 nox에서 64비트 모듈을 무시하고 32비트 모듈을 호출하게 되니 주의

3. nox 64비트 다운로드 (32비트를 받으면 arm64-v8a 에뮬레이션이 불가능)

4. 옵션에서 ROOT권한 주고, 개발자 옵션에서 USB 디버깅 허용 (구글에서 "nox 안드로이드 분석 환경" 로 검색해보면 가이드 많이 나옴)

5. 안드로이드 스튜디오에서 nox 기기를 apk 실행용으로 설정한다.

6. 아래와 같이 arm64-v8a 환경에서 잘 동작하는지 확인한다.

1) 아래는 arm64-v8a 전용 어셈블리어이므로, 타 cpu환경에서는 동작하지 않는다.

extern "C" JNIEXPORT jstring JNICALL
Java_com_codetronik_myapplication5_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";

__asm__ (".byte 0x03\n"\
        ".byte 0x00\n"\
        ".byte 0x00\n"\
        ".byte 0x14\n"\
        ".byte 0xde\n"\
        ".byte 0xad\n"\
        ".byte 0xbe\n"\
        ".byte 0xef\n"\
        ".byte 0xde\n"\
        ".byte 0xad\n"\
        ".byte 0xbe\n"\
        ".byte 0xef\n");

    return env->NewStringUTF(hello.c_str());
}

2) nox-adb을 root로 실행 후 빌드한 jni가 arm64로 동작 중인지 확인한다.

갤탭에서 잘됨

gdb-arm-static-7.11
4.44MB

1. namespace 체크 때문에 so를 /system/lib에 복사하여야 한다.

2. so에서 로그 파일 생성 시 로그파일 경로가 app경로가 아닐 경우 crash가 발생한다. /data/data/app_name/cache 에 로그를 생성하면 오류가 발생하지 않는다.

android 11의 경우 /system/lib64 디렉토리도 namespace 우회가 불가능하기 때문에 /data/user/0/app name/files 폴더에 so를 복사한다.

 

ARM의 코드 실행 모드는 x86과는 다르게 2 or 4바이트씩 처리하는 thumb모드, 4바이트씩 처리하는 arm모드, 두 종류로 나뉩니다.

안드로이드에서 동작하는 대부분의 프로그램(executable)들은 arm과 thumb이 결합된 모드로 컴파일 되어 있습니다.

한 프로그램 내에서 함수에 따라 모드가 달라질 수 있습니다.

아래의 예시 그림은 동일한 파일임에도 각각 다른 모드로 컴파일 되었음을 보여줍니다

THUMB MODE

ARM MODE


x86 후킹과 달리 ARM 후킹은 코드 재배치를 고려해야 하는 경우가 많습니다.

그 이유는 x86은 점프에 필요한 바이트가 단지 5바이트이고,  함수의 프롤로그가 아래와 유사한 경우가 많습니다.

8B FF MOV EDI, EDI

55     PUSH EBP

88 EC MOV EBP, ESP

위의 5바이트는 매우 단순하며, 점프 주소에 복사되어 실행되어도 아무런 문제가 없습니다.


반면, THUMB모드 후킹의 경우는 8바이트가 필요하며(명령어 4바이트, 점프 주소 4바이트), 함수의 프롤로그가 아래와 유사한 경우가 많습니다. (arm 모드 후킹은 추후 다루겠습니다.)

F0 B5 PUSH { R4-R7, LR }

위의 2바이트는 점프 주소에 복사되어 실행되어도 문제없어 보이지만, 이어지는 6바이트가 실행될 때 crash가 야기될 수 있습니다.

아래와 같은 함수가 있다고 가정합니다.

F0 B5 PUSH { R4-R7, LR}

1F 1C MOVS R7, R3

A8 23 MOVS R3, #168

78 4C LDR R4, [PC, #480]

... 후략


7, 8바이트에서 PC + 480 의 값을 참조합니다. 만약 해당 코드가 점프 주소에 그대로 복사되어 실행된다면 엉뚱한 값을 참조하게 되어 결국 의도하지 않은 지점에서 crash가 발생하게 될 것입니다. 

이해를 돕기 위한 그림을 첨부합니다.



이러한 문제들이 발생할 수 있는 가능성이 있기 때문에, 코드 재배치를 해주어야 합니다.

재배치가 고려되어야 할 코드들은 아래와 같습니다.

 THUMB16

 THUMB32

ARM 

 B <label>

 심심할때 작성..

심심할때 작성.. 

 BX PC

   

 ADD <Rdn>, PC

   

 MOV Rd, PC

   

 ADR Rd, <label>

   

 LDR Rt, <label>

   

 CB{N}Z <Rn>, <label>

 


오리지널 코드 4번째 줄에 LDR이 보이므로, 코드 재배치를 해야 합니다.

우선 특정 주소에서 값을 가져와서 r4에 저장할 코드의 일반적인 형태는 다음과 같습니다.

00 4C  ldr r4, [PC] <-- 0xdeadbeef를 취함

01 E0  B PC, #2 <-- 값을 취했으므로 다음 코드를 실행 (B PC, #2)

ef be ad de  <-- 원래의 함수에서 의도된 값

0xdeadbeef는 아래와 같은 수식으로 취할 수 있습니다.

value = (unsigned int*) (Align(pc) + (앞 1바이트 * 2));


구체적으로 풀어보면,

0x78 0x4c에서 0x78만 취한다음, *2를 합니다. (=480) 그 다음 pc를 4바이트 단위로 align합니다.


align 예시)

 PC 

 PC align

 0x87F0 

 0x87F0 

 0x87F2

 0x87F0 

 0x87F4

 0x87F4 

 0x87F6 

 0x87F4 

 0x87F8

 0x87F8


0x87F2:  ldr     r4, [pc, #480]  일 경우, pc는 0x87F4가 됩니다.


0x87F4(align 하였음) + 480(0x1e0) = 0x89d4 를 살펴보면..

0x89D4: C4 FA FF FF     가 보이네요.  

0xFFFFFAC4를 취하면 됩니다. (위의 그림 참조)


아래와 같이 재배치를 할 수 있습니다. 재배치가 필요 없는 코드는 그대로 복사하면 됩니다. (malloc 후에 재배치)


0x789e0001:  f0 b5   push    {r4, r5, r6, r7, lr}

0x789e0003:  1f 1c   adds    r7, r3, #0

0x789e0005:  a8 23   movs    r3, #168     

0x789e0007:  00 4c   ldr     r4, [pc, #0]    ; (0x789e0008) 

0x789e0009:  01 e0   b.n     0x789e000e 

0x789e000b:  c4 fa ff ff   


그런데 뭔가 이상하군요. 0x789e0007 를 보시면 pc + 0x0라인 지점이 영 좋지 않은 곳을 가리켜서 crash가 발생합니다. 의도한대로라면 0x789e000b을 가리켜야 하는데 말이죠. 

이는 2바이트씩 align이 되어 발생한 문제입니다. 이를 해결하려면 thumb16 코드(4바이트 코드는 thumb32라 합니다.) 중간중간에 nop를 삽입시켜주면 됩니다.

0x78dfc001:  f0 b5   push    {r4, r5, r6, r7, lr}

0x78dfc003:  00 bf   nop

0x78dfc005:  1f 1c   adds    r7, r3, #0

0x78dfc007:  00 bf   nop

0x78dfc009:  a8 23   movs    r3, #168        ; 0xa8

0x78dfc00b:  00 bf   nop

0x78dfc00d:  00 4c   ldr     r4, [pc, #0]    ; (0x78dfc010) // (thumb모드이므로 해석된 주소에서 +1씩 더해서 계산해야 합니다.)

0x78dfc00f:  01 e0   b.n     0x78dfc014

0x78dfc011:  c4 fa ff ff         


이제 잘 동작합니다. GOOD! 



[TIP]

gdb 사용시 align을 맞추기 위해 아래 명령어를 실행해 주세요. 한 바이트씩 밀리는 것을 방지하여 줍니다.

(gdb) set arm fallback-mode thumb 

특정 주소를 보는 방법은 dias /r 0xStart 0xEnd

메모리 및 파일에 프로세스에 매핑 된 classes.dex를 저장하는 방법입니다.
저장 중에 ptrace가 실패할 경우 0xdeadbeef 가 저장됩니다.


본 포스팅에서는 dalvik 바이트코드를 메모리에서 변조하는 방법을 다룹니다.


필자의 테스트 환경

- galaxy S5 

- Android 4.4.2

- Snapdragon 805 (32bit) 


"안드로이드 후킹, 메모리 변조 : frida framework를 활용한 dex 함수 후킹" 포스트에서 사용한 apk를 그대로 사용합니다.

우선, ida로 classes.dex를 연 다음 check_file 함수를 찾습니다. su 파일 경로를 인자로 전달받아, 파일이 존재하면 true를 리턴하는 함수입니다.

이 함수를 무조건 false를 리턴하게 하면 되겠지요.

현재 바이트 파싱이 big endian으로 되어있기 때문에 바이트를 뒤집어서 보셔야 합니다.

각 바이트들이 무엇을 의미하는지는 http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html 에 자세하게 설명되어 있습니다.

함수의 마지막 줄을 보면 v0을 리턴하게 됩니다. 참고로 0이 true이고 1이 false입니다.

v0이 0또는 1로 설정되어 리턴하는 것을 보실 수 있습니다.

첫 줄을 보면, hex 0x12는 4비트를 변수에 설정하는 opcode입니다.

보통 0x12 0xXY 형태로 사용합니다. 

X는 설정할 값이고, Y는 변수입니다.

Y=0이면 v0, Y=1이면 v1, Y=2이면 v2 이런 식입니다.

우리는 v0 값을 false로 설정할 것이므로, 0x12 0x10 으로 메모리를 변조하면 됩니다.

0x12 0x10 수행 후, 0x00 0x0F 를 통해 리턴을 하게 하면 무조건 false를 리턴하게 되겠지요.


제, classes.dex 메모리를 ptrace를 통해 변조하면 됩니다.

classes.dex 파일에서는 check_file 함수의 offset이 0x132C60이었는데, 메모리에 매핑될 때는 앞에 부가적인 데이터들이 삽입됩니다. (0x28 바이트 고정)

이 데이터들을 건너뛰면, 해당 함수의 offset은 "dex" signature offset으로부터 0x132C60에 위치하게 됩니다. 

아래 그림은 0x76958000 를 덤프한 그림입니다. (메모리 덤프 방법은 http://codetronik.tistory.com/118 참조하세요.

아래 코드와 같이 dex 메모리를 변조할 수 있습니다.

ptrace에서 메모리를 read/write할 때, 사이즈 단위가 long이므로 (테스트한 단말기 환경에서는 4바이트), 아래의 PtraceWrite와 같은 패치 코드를 나눠서 write할 수 있는 함수를 작성해야 합니다.


정상적으로 패치가 되면 아래 화면처럼 null이 나타납니다.



본 포스팅에서는 FRIDA 프레임워크를 통한 후킹 방법을 설명합니다.

FRIDA를 설치하기 위해선 반드시 ROOT 권한이 필요합니다. 각자 환경에 맞는 루팅을 진행해주시면 됩니다.

 

필자의 테스트 환경

- galaxy S5 

- Android 4.4.2

- Snapdragon 805 (32bit) 

 

설치 준비물은 아래와 같습니다.

- Python 3.7

- frida-server-xx.y.zz-android-arm.xz (https://github.com/frida/frida/releases 에서 각자 환경에 맞는 최신 자료를 다운로드)

(갤럭시탭A 2017은 14.2.13버전 적합)

 

1. 단말기에 설치

adb shell 접속 후, /system의 read-only 권한을 쓰기 권한으로 변경하여야 합니다.

모든 권한은 항상 ROOT입니다.

 

mount -o rw,remount /system 

(에러 발생 시 mount -o rw,remount / )

/system/priv-app 에 xz파일 압축 해제

mount -o ro,remount /system

 

단말기를 재부팅한 후에 서버를 구동합니다.

/system/priv-app/frida-server &

(remote 시 /frida-server -l(엘) 아이피 &

 

2. 윈도우에 설치

파이선 설치는 가능하면 d:\python와 같은 짧은 경로로 설치합니다.

설치가 끝나면 아래 명령어를 실행합니다.

c:\>pip install frida

c:\> pip install frida-tools // frida.exe를 설치해주며(필수), 최신버전 frida도 함께 설치해줌

(특징 버전 설치: pip install frida==14.2.13)

설치가 정상적으로 되었다면 frida-ps -U 로 안드로이드 내의 프로세스가 보이는지 확인합니다.

(remote 시 frida-ps -H 아이피)

 

이제 모든 준비가 끝났습니다. 이제부터 실전 예제를 통해 후킹 방법을 설명합니다.

제가 준비한 예제는 다음과 같습니다. apk를 단말기에 설치합니다.

 

- 루팅 감지

소스 : https://github.com/codetronik/RootChecker

codetronik.rootchecker.apk
다운로드

 

 


 

후킹에 앞서, dex 디컴파일러로 dex의 구조를 파악해봅니다.

무료 디컴파일러 중에선 jadx가 유용합니다.

 

jadx로 열어보면 위와 같은 소스코드가 보입니다. frida로 하나씩 후킹해보도록 하겠습니다.

후킹 소스의 기본 틀은 아래와 같습니다.

 
 
후킹 코드들은 Java.perform(function(){ 후킹 코드 }); 와 같은 형태로 작성하면 됩니다.

execute_su() 함수는 su가 실행이 되면 true를 리턴합니다. 아래 코드로 우회할 수 있습니다.

단말기에서 앱을 실행 후, 후킹 소스를 실행하여 실행결과를 비교해보면 화면의 check execute su : YES에서 NO로 바뀐 것을 확인해볼 수 있습니다.

check_su_files() 함수는 su 파일이 존재하면 해당 경로를 리턴하고, 없으면 null을 리턴합니다. 마찬가지로 return null을 하면 간단히 우회할 수 있으나 임의의 경로를 인자로 넘기는 방법도 있습니다.

후킹할 함수에 인자가 있는 경우, 적절하게 overload를 해주시면 됩니다.  

 Byte[]   [B
 String  java.lang.String
 int  int


  

 

팁: 소스에서 Java.perform(function( ~~ ){}); 이외는 삭제하고, 아래와 같이 실행하면 frida가 앱을 실행하면서 후킹을 합니다.

앱 띄우면서 중단없이 후킹

frida -H 아이피 -f 앱이름 -l 소스파일명 --no-paus (원격)

frida -U -f 앱이름 -l 소스파일명 --no-paus (usb)
 

있던 프로세스에 attach

frida -H 아이피 앱이름 -l 소스파일명 --no-paus

android 11에서 spawn시 Error: VM::AttachCurrentThread failed: -1가 발생할 경우 쉘에서 아래 커맨드를 입력

setprop persist.device_config.runtime_native.usap_pool_enabled false

커널 4.6에서 커널 4.5까지 존재했던 crypto_hash 관련 함수가 사라졌습니다.

참조(사라진 함수..) : http://elixir.free-electrons.com/linux/v4.6-rc1/ident/crypto_alloc_hash

계속 사용하고 싶다면 비동기(crypto_ahash), 동기(crypto_shash) 중에 선택하여 사용하여야 합니다.

본 예제에서는 동기를 다룹니다. (kernel 4.10.0-38 에서 테스트 되었습니다.)



+ Recent posts