2016년 5월 22일 일요일

apk hack

서론


제목은 hack 이지만 이번에 다룰 주제는 apk를 분해하고 수정 후 재signing하고 다시 apk를 만들어 볼 예정입니다.

그럼 이런것을 언제 사용해야할까요?
아주 훌륭한 apk가 있습니다. 물론 자신이 만든 apk가 아니라고 했을때 해당 apk를 수정해서 새로운 apk를 만드는 일은 쉬운일이 아닙니다.
여기에서는 타인이 만든 apk와 자신의 apk을 연결하는 부분에 대해서 고민해보고 예제를 들어보도록 하겠습니다.


준비물


apktool : A tool for reverse engineering Android apk files, apk파일을 decompile 및 재조립 할때 사용합니다. 아래 링크에서 최신버전을 다운받습니다. 내용을 보면 jdk 버전에 따라서 다른 버전을 사용하라고 되어있는데요. 해보니 잘안되어서 최신 버전을 받으니 잘됩니다.
http://ibotpeaches.github.io/Apktool/
현시점 최신버전 2.1.1 버전을 받았습니다.

jdk : apktool 을 실행시킬때 사용합니다.
설치된 java 버전이 1.8이네요.
C:\>java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) Client VM (build 25.45-b02, mixed mode, sharing)

android studio : 샘플 프로그램 제작용


시작하기


샘플 프로그램을 만듭니다. 타인이 만들었다고 생각하는 apk입니다. hack할 대상이 되는 apk입니다. 해당 apk에서는 주기적으로 화면에 값을 출력하는 apk입니다.

MainActivity.java:소스
package examples.arts.g.d.othersapk;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;


public class MainActivity extends ActionBarActivity {
    int value=0;
    TextView mText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mText=(TextView)findViewById(R.id.text);
        mHandler.sendEmptyMessage(0);
    }
    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            value++;
            mText.setText("Value = " + value);
            mHandler.sendEmptyMessageDelayed(0, 1000);
        }
    };

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

activity_main.xml 소스
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Timer"
        />
</RelativeLayout>

실행화면
가운데 있는 숫자가 초단위로 증가하는 소스 입니다.



apk decompile

java가 path에 추가가 되어있다면 아래와 같이하면 됩니다.

D:\download\apktooltest>java -jar apktool_2.1.1.jar d othersapk.apk

othersapk.apk는 위에서 만든 apk이름이며, 실행하면 othersapk 폴더가 만들어지며 decompile이 된겁니다.

코드 추가 고민하기

decompile된 소스 코드는 smali 확장자로 되어있으며 java vm 코드형태로 보기가 좀 어렵습니다. 해당 코드를 수정하려면 약간의 편법이 필요합니다. sample 프로그램을 만들어 비교해서 추가하면 됩니다.
또 다시 샘플 프로그램을 만들어 보겠습니다.
인텐트를 받을 수 있는 서비스 하나랑, 서비스로 인텐트를 보내주는 apk 그리고 인텐트를 보내주지 않는 apk 3개를 만들어 뒤쪽 2개는 smali 코드를 비교해 차이나는 부분만 othersapk에 추가해서  othersapk를 apk를 만들 계획입니다.

서비스 만들기

MyService를 만들고 onStartCommand가 호출되면 로그를 출력하도록 제작합니다.
package examples.arts.g.d.hackservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    private static final String TAG = "MainService";
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int data = intent.getIntExtra("int",0);
        Log.e(TAG, "in onStartCommand " + data);
        return START_STICKY;
    }
}

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="examples.arts.g.d.hackservice" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true" >
        </service>
    </application>

</manifest>

호출해주는 APK 만들기

SendIntent 라는 이름으로 만들어 보겠습니다. StartService 메소드가 핵심입니다.
package examples.arts.g.d.sendintent;

import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;


public class MainActivity extends ActionBarActivity {
    int abc = 99;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        abc = 99;
        StartService(abc);
    }

    private void StartService(int value) {
        Intent intent2 = new Intent();
        intent2.setClassName("examples.arts.g.d.hackservice", "examples.arts.g.d.hackservice.MyService");
        intent2.putExtra("int",value);
        this.startService(intent2);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

두개의 apk가 설치되고 SendIntent를 실행시면 아래와 같은 문구가 로그상에 보이면 이제 준비는 끝나게됩니다.
05-22 09:21:51.794  11444-11444/? E/MainService﹕ in onStartCommand 99

SendIntent RM 소스, 호출해주는 부분만 막힌 소스 입니다.
package examples.arts.g.d.sendintent;

import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;


public class MainActivity extends ActionBarActivity {
//    int abc = 99;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        abc = 99;
//        StartService(abc);
    }

//    private void StartService(int value) {
//        Intent intent2 = new Intent();
//        intent2.setClassName("examples.arts.g.d.hackservice", "examples.arts.g.d.hackservice.MyService");
//        intent2.putExtra("int",value);
//        this.startService(intent2);
//    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

소스 비교해보기


D:\>java -jar apktool_2.1.1.jar d sendintent.apk
D:\>java -jar apktool_2.1.1.jar d sendintent_rm.apk
비교해보면 크게 세부분이 다릅니다. .method private StartService(I)V 이부분인데 StartService 함수를 만든거니까 당연히 다를겁니다. 그리고 한부부분은 호출 해주는 부분 입니다.



smali 수정하기

othersapk MainActivity.smali 소스에 추가하면됩니다.
smali 문법에 대해서는 여기저기 검색해보면 됩니다.
v0,v1...등은 레지스터 변수를 의미합니다.
MainActivity$1.smali  <- MainActivity.smali의 inner 클래스 입니다. Handler 때문에 생성되었습니다. 아래과정이 제일 힘든 과정입니다. 소스를 분석해야 하고 적당한 위치와 smali 문법에 대해 지식이 있어야 하는 부분입니다.
# virtual methods
.method public handleMessage(Landroid/os/Message;)V
    .locals 4
    .param p1, "msg"    # Landroid/os/Message;

    .prologue
    .line 24
    iget-object v0, p0, Lexamples/arts/g/d/othersapk/MainActivity$1;->this$0:Lexamples/arts/g/d/othersapk/MainActivity;

    iget v1, v0, Lexamples/arts/g/d/othersapk/MainActivity;->value:I

    add-int/lit8 v1, v1, 0x1

    iput v1, v0, Lexamples/arts/g/d/othersapk/MainActivity;->value:I

    #inject start
    invoke-virtual {v0, v1}, Lexamples/arts/g/d/othersapk/MainActivity;->StartService(I)V
    #inject end

MainActivity.smali 에 추가합니다.
.method public StartService(I)V
    .locals 3
    .param p1, "value"    # I

    .prologue
    .line 21
    new-instance v0, Landroid/content/Intent;

    invoke-direct {v0}, Landroid/content/Intent;-><init>()V

    .line 22
    .local v0, "intent2":Landroid/content/Intent;
    const-string v1, "examples.arts.g.d.hackservice"

    const-string v2, "examples.arts.g.d.hackservice.MyService"

    invoke-virtual {v0, v1, v2}, Landroid/content/Intent;->setClassName(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;

    .line 23
    const-string v1, "int"

    invoke-virtual {v0, v1, p1}, Landroid/content/Intent;->putExtra(Ljava/lang/String;I)Landroid/content/Intent;

    .line 24
    invoke-virtual {p0, v0}, Lexamples/arts/g/d/othersapk/MainActivity;->startService(Landroid/content/Intent;)Landroid/content/ComponentName;

    .line 25
    return-void
.end method

빌드하기

java -jar apktool_2.1.1.jar b othersapk_mod
결과파일은 othersapk_mod\dist 여기 폴더에 apk파일이 생성됩니다.

재 signing 하기

signing을 하기위해서는 key가 필요합니다. 일반적으로 키를 가지고 있지 않다면 android에서 제공하는 debug키를 사용하면 됩니다. android SDK가 정상적으로 설치되었다면 아래 경로에 있습니다.
C:\Users\사용자이름\.android\debug.keystore
해당파일을 작업 폴더에 복사해두고 signing을 합니다.
D:\download\apktooltest>"C:\Program Files (x86)\Java\jdk1.8.0_45\bin\jarsigner.exe" -keystore debug.keystore othersapk_mod\dist\othersapk.apk androiddebugkey

Enter Passphrase for keystore: 가뜨면 android 라고 쳐줍니다. debugkey의 비밀번호입니다.

아래 툴을 이용해도 됩니다.
https://shatter-box.com/knowledgebase/android-apk-signing-tool-apk-signer/


실행하기

아래와같은 배치 파일을 만들어서 apk를 만듭니다.
make.bat 내용
java -jar apktool_2.1.1.jar b othersapk_mod
"C:\Program Files (x86)\Java\jdk1.8.0_45\bin\jarsigner.exe" -keystore debug.keystore othersapk_mod\dist\othersapk.apk androiddebugkey

실행시 아래와 같은 로그가 나오게 되면 정상 동작하는것입니다.
05-22 13:29:19.698: E/MainService(9272): in onStartCommand 86
05-22 13:29:20.699: E/MainService(9272): in onStartCommand 87
05-22 13:29:21.710: E/MainService(9272): in onStartCommand 88
05-22 13:29:22.721: E/MainService(9272): in onStartCommand 89
05-22 13:29:23.742: E/MainService(9272): in onStartCommand 90
05-22 13:29:24.753: E/MainService(9272): in onStartCommand 91
05-22 13:29:25.774: E/MainService(9272): in onStartCommand 92
05-22 13:29:26.795: E/MainService(9272): in onStartCommand 93
05-22 13:29:27.807: E/MainService(9272): in onStartCommand 94
05-22 13:29:28.818: E/MainService(9272): in onStartCommand 95
05-22 13:29:29.829: E/MainService(9272): in onStartCommand 96
05-22 13:29:30.850: E/MainService(9272): in onStartCommand 97
05-22 13:29:31.871: E/MainService(9272): in onStartCommand 98
05-22 13:29:32.892: E/MainService(9272): in onStartCommand 99
05-22 13:29:33.903: E/MainService(9272): in onStartCommand 100
05-22 13:29:34.924: E/MainService(9272): in onStartCommand 101
05-22 13:29:35.945: E/MainService(9272): in onStartCommand 102
05-22 13:29:36.966: E/MainService(9272): in onStartCommand 103

이것으로 apk를 hack 하는 내용을 마치겠습니다.

작업된예제링크

댓글 없음:

댓글 쓰기