Computer Science%/Mobile App

Android Debugging 방법

ch4rli3kop 2020. 3. 6. 10:38
반응형

Android Debugging 방법

안드로이드 어플을 디버깅하는 방법을 간단하게 정리해보려고 합니다. IDA를 사용하는 방법, Android Studio를 사용하는 방법 등 다양한 방법이 존재하는데, 우선 일차적으로 IDA를 사용하여 Android application process에 attach하는 방법을 서술한 뒤, 추후 다양한 방법들을 시도해보며 프로젝트에 활용할 수 있는 다른 방법들 역시 덧붙이도록 하겠습니다.

편의상 windows cmd 창을 >로 표기하였으니 혼동하시지 마시길 바랍니다.

Index

  • IDA를 이용한 Android app 동적 분석

IDA를 이용한 Android app 동적 분석

다음은 갓갓 IDA를 이용한 Android application 동적 분석 방법을 설명합니다. 환경은 windows 10이며, 분석 대상은 Where.apk 테스트 게임으로 진행하도록 하겠습니다.

App에 debug 옵션을 추가한 뒤 리패키징하기

app을 동적으로 분석하기 위해서는 우선적으로 해당 app에 debug 옵션이 제공되어야 합니다. 해당 정보는 apktools를 이용하여 app을 decompile 한 뒤, 바로 보이는 AndroidManifest.xml 파일에서 찾을 수 있습니다.

다음의 일련의 과정을 통해 android apk 파일에 debug 옵션을 추가하도록 할 수 있습니다.

1. apktools로 apk 파일 decompile 하기

apktools를 이용하여 application을 디컴파일합니다. (java가 설치되어 있어야 함.)

> java -jar apktool_2.4.0.jar d where.apk 

 

2. AndroidManifest.xml파일에 debug 속성 추가하기

<?xml version="1.0" encoding="utf-8" standalone="no"?>
...
   <application android:icon="@mipmap/app_icon" android:debuggable="true" android:label="@string/app_name" android:theme="@style/UnityThemeSelector">
...

application 태그 내에 위와 같이 android:debuggable="true"를 추가한 뒤 저장합니다.

3. 다시 build 해주기

apktools를 이용하여 수정한 application data를 컴파일 합니다.

> java -jar apktool_2.4.0.jar b [directory name] -o where_debug.apk

 

4. 빌드된 apk에 서명해주기

Android는 서명된 apk에 한하여 설치할 수 있으므로 signapk를 통하여 빌드된 app에 서명을 해줘야 합니다.

> java -jar signapk.jar testkey.x509.pem testkey.pk8 where_debug.apk where_debug_signed.apk

adb 연결 및 IDA android_server 실행시키기

adb는 Android Debug Bridge의 줄임말로, 사용자가 기기와 통신할 수 있도록 unix shell과 같은 명령줄 도구를 제공해줄 수 있는 다목적 클라이언트-서버 프로그램입니다. 간단하게 nc와 같은 프로그램을 떠올리셔도 될 것 같습니다. 자세한 사항은 다음에서 확인해주세요.

adb는 android sdk를 통해 설치할 수 있습니다.

adb가 설치되었다면 스마트 폰과 PC를 usb 케이블을 이용하여 연결한 뒤, cmd에서 다음과 같은 명령어를 실행해주세요. 이 때 스마트 폰은 디버깅 모드로 되어 있어야 합니다. 스마트 폰의 개발자 모드를 통해 해당 설정을 활성화 시킬 수 있습니다.

> adb devices
* daemon not running; starting now at tcp:5037
* daemon started successfully
List of devices attached
LGF470K5d6**** device


정상적으로 연결이 되었다면 위와 같은 결과를 얻을 수 있습니다. 만약 장치를 찾을 수 없거나, unauthorized 가 표시된다면 장치 드라이버가 제대로 설치되지 않은 문제이니 제대로 된 드라이버를 설치해주시길 바랍니다.

1. adb shell 접속

다음과 같이 스마트 폰에 adb를 통해 접속이 가능합니다. 루팅이 되지않았다면 루트로 접근할 수 없습니다.

> adb shell
shell@jagn:/ $ su root
root@jagn:/ # id
uid=0(root) gid=0(root) context=u:r:init:s0
root@jagn:/ #


2. Android_server 실행하기

adb가 제대로 실행이 된다면 이제 adb에서 빠져나와 다시 cmd 창으로 돌아가주세요.

이제 IDA에서 remote debugging을 하기위한 사전 준비를 진행해야 합니다. IDA가 설치된 디렉토리 내부의 dbgsrv로 들어갑니다. 디폴트 경로는 C:\Program Files\IDA 7.2\dbgsrv 입니다.

해당 디렉토리 내부에는 다양한 IDA remote debug server가 존재하는데, 이 중에 자신의 기기에 맞는 android_server를 선택하여 기기 내에 넣어줍니다.

다음의 명령어를 통해 디버깅을 위한 사전 준비를 맞출 수 있습니다.

> & 'D:\Program Files\Nox\bin\adb.exe' forward tcp:23946 tcp:23946

C:\Program Files\IDA 7.2\dbgsrv>adb push android_server /data/local/tmp
android_server: 1 file pushed. 2.4 MB/s (622428 bytes in 0.244s)
​
C:\Program Files\IDA 7.2\dbgsrv>adb shell
shell@jagn:/ $ su root
root@jagn:/ # cd /data/local/tmp
root@jagn:/data/local/tmp # ls
android_server
​
root@jagn:/data/local/tmp # chmod 755 android_server
root@jagn:/data/local/tmp # ./android_server
IDA Android 32-bit remote debug server(ST) v1.25. Hex-Rays (c) 2004-2018
Listening on 0.0.0.0:23946...


IDA로 attach 하기

이제 IDA에서 열려있는 서버에 접근하여 실행되고 있는 프로세스에 attach가 가능합니다. 다음과 같이 IDA의 debugger 항목에서 Linux/Android debugger를 선택해주시면 됩니다.

다음과 같은 창이 뜰 터인데, adb shell에 접속한 상태에서 network를 확인하여 알아낸 ip 주소를 적으면 됩니다.

C:\Users\pch21>adb shell
shell@jagn:/ $ netcfg
...
lo       UP                                   127.0.0.1/8   0x00000049 00:00:00:00:00:00
wlan0   UP                                192.168.1.20/22 0x00001043 XX:XX:XX:XX:XX:XX
...
shell@jagn:/ $


저의 경우 192.168.1.20 이었습니다.

OK를 누르면 다음과 같이 해당 기기에서 실행 중인 프로세스 목록이 보이는데, 여기에서 디버깅할 프로세스를 선택하여 진행하시면 됩니다.

ㅅㅅ

 

아 미리 좀 옮겨놓을걸.. 이제와서 옮기기 너무 귀찮타... ㅠㅠ

 

[+] .so 디버깅하기 (Frida를 곁들인)

so만 직접적으로 실행할 수 없기 때문에, 앱을 먼저 키고 so를 로드할 때 IDA를 붙여놔야 한다.

사실 Frida를 사용하는 건 다음과 같이 앱을 spawn 시켜 앱의 메인 쓰레드가 시작되기 직전에 멈추도록 하여 IDA가 attach로 붙을 수 있게 하기 위함이다.

frida -l .\test.js -f com.example.attackapp -U

다음과 같이 Frida 스크립트를 실행해서 so가 로드될 때를 확인해도 좋다.

아무튼 다음과 같이 하면 된다.

1. Frida로 앱을 spawn 시키면 메인 쓰레드가 시작되기 직전 멈춘다.
2. 디버깅 준비가 IDA로 해당 어플리케이션의 프로세스에 attach 한다. (물론 breakpoint는 설정해놓는다.)
3. Frida에서 %resume 명령어로 프로세스를 재개한다.
4. IDA에서 어플리케이션 디렉토리에 있는 *.so 파일과 지금 보고 있는 *.so 파일이 동일하냐고 물어보는 알럿창이 뜬다. same 버튼을 누르면 된다.
5. IDA에서 계속 버튼을 누르면 breakpoint에 멈춘다.

 

// var foo = Module.getBaseAddress('libbinary.so');
// if (!foo){
//     console.log('libfoo not loaded!');
// }
// console.log('[+] libfoo.so @ ' + foo.toString());

// function native_hook(){
//     var foo = Module.getBaseAddress('libbinary.so');
//     console.log('[+] libfoo.so @ ' + foo.toString());
// }

// setImmediate(function(){
//     Java.performNow(function(){
//         console.log('Hooking Start!');
//         native_hook();

//         // if (!foo){
//         //     console.log('[+] libfoo.so @ ' + foo.toString());
//         // }
//    });
// }, 1000);



var library_name = "libbinary.so"
var library_loaded = 0
Interceptor.attach(Module.findExportByName(null, 'android_dlopen_ext'),{
    onEnter: function(args){
        // first arg is the path to the library loaded
        var library_path = Memory.readCString(args[0])

        if( library_path.includes(library_name)){
            console.log("[...] Loading library : " + library_path)
            library_loaded = 1
        }
    },
    onLeave: function(args){

        // if it's the library we want to hook, hooking it
        if(library_loaded ==  1){ // 원하는 library가 로딩되었을때
            var foo = Module.getBaseAddress('libbinary.so');
            console.log('[+] libfoo.so @ ' + foo.toString());
            var vall = ptr(foo)
            var send = 0x29E290
            var printf = 0x0297AD0

            var target = foo.add(0x183800);
            Interceptor.attach(target, {
                onEnter: function(args){
                    console.log('Qiasdfasdfs process run');
                    // console.log(vall.add(0x34AA54));
                    // var globalv = Memory.readByteArray(vall.add(0x34AA54), 0x4);
                    // console.log(vall.add(0x34AA54));
                    // console.log('change before : ', globalv);
                    // var data = ['\x00','\x22','\x22','\x22',];
                    // Memory.writeByteArray(vall.add(0x34AA54), data);
                    // var globalv = Memory.readByteArray(vall.add(0x34AA54), 0x4);
                    // console.log('change after : ', globalv);
                },
                onLeave: function(args){
                    console.log('return')
                }
            })

            var target2 = foo.add(send);
            Interceptor.attach(target2, {
                onEnter: function(args){
                    console.log('send run');
                },
                onLeave: function(args){
                    console.log('send return')
                }
            })
            var target3 = foo.add(printf);
            Interceptor.attach(target3, {
                onEnter: function(args){
                    console.log('print run');
                    console.log(args[0]);
                },
                onLeave: function(args){
                    console.log('print return')
                }
            })


            library_loaded = 0
        }
    }
})


// function native_hook(){
//    var foo = Module.getBaseAddress('libbinary.so');
//    if (!foo){
//        console.log('libfoo not loaded!');
//        return 0;
//   }
//    console.log('[+] libfoo.so @ ' + foo.toString());
// //    var target = foo.add(0xfa0);
// //    var save;
// //    Interceptor.attach(target, {
// //        onEnter: function(args){
// //            console.log('OnEnter :');
// //            save = ptr(args[0]);
// //            console.log(save);
// //            // console.log(Memory.read)
// //       },
// //        onLeave: function(retval){
// //            console.log('OnLeave :');
// //            var data2 = Memory.readByteArray(save, 0x18);
// //            console.log(data2);
// //            var data = new Uint8Array(data2);
// //            var result = '';
// //            for (var i=0; i<0x18; i++){
// //                var tmp = String.fromCharCode(data[i] ^ xor_key[i].charCodeAt());
// //                //console.log(tmp);
// //                result += tmp;
// //           }
// //            console.log(result);
// //       }
// //   });
// }
// ​
// setImmediate(function(){
//    Java.performNow(function(){
//        console.log('Hooking Start!');
       
//        native_hook();
   
//        // while(true){
//        //     try{
//        //         var foo = Module.getBaseAddress('libfoo.so');
//        //         console.log('[+] libfoo.so @ ' + foo.toString());
//        //         break;
//        //     } catch (e){
//        //         continue;
//        //     }
//        // }
//        // if (!foo){
//        //     console.log('[+] libfoo.so @ ' + foo.toString());
//        // }
   
//   });
// ​
// });

 

반응형