スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。



Android アプリ内課金でProguardを使用するとき

Androidのアプリ内課金を実装してProguardを使用するとき,
Proguardの設定ファイルに追記しておいてと言われているものがあります。

公式リファレンスには以下のように書かれています。

Note: If you use Proguard to obfuscate your code, you must add the following line to your Proguard configuration file:
-keep class com.android.vending.billing.**



ここでいう設定ファイルはプロジェクト直下にあるproguard-project.txt。
このファイルにリファレンス通りに設定を書いて難読化してみると
普通に難読化されてました。

へ?

リファレンスを信じて検証していませんでしたが,どうやらこの設定ではGoogleが意図していた
aidlから自動生成されたインタフェースなどが難読化から除外されないようです。

とりあえず直書きで以下のように設定して対応しました。

-keep public interface com.android.vending.billing.IMarketBillingService
-keep public class com.android.vending.billing.IMarketBillingService$Stub
-keep class com.android.vending.billing.IMarketBillingService$Stub$Proxy

Googleの設定ではclassをキープしたかったように見えますが
多分インタフェース名だと思います。
自動生成されたIMarcketBillingServiceの内部クラスStubのメンバにはDESCRIPTORが設定されていて,
ここにインターフェースのパッケージ名が書かれています。

サービス接続時(onServiceConnected)に渡される引数IBinderからインターフェースを検索取得して
IMarketBillingServiceへキャストしている。
難読化しているときはキャスト前の型チェック(instanceof)で失敗していると考えられる。

ただそのときはaidlによって自動生成されたIMarketBillingService.javaに定義されている
Proxyクラスによって購入リクエストが実行できるようになっている...と考えられる。

難読化しても実行できるように組まれているんだけど,
リモート側の実装が変わったときに柔軟に対応できないんじゃないかな。

そのために難読化するときはIMarketBillingService.javaに定義されているクラスは除外してね
と言っているに違いない。
(ただIMarketBillingServiceへキャストできればよいので難読化対象外にするのはIMarketBillingServiceインタフェースだけで良さそう)

aidlで自動生成されたIMarketBillingService.java
/*
 * This file is auto-generated. DO NOT MODIFY. Original file:
 * /Users/junji/Develop
 * /workspace/BillingLibrary/src/com/android/vending/billing/
 * IMarketBillingService.aidl
 */
package com.android.vending.billing;

public interface IMarketBillingService extends android.os.IInterface
{
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements com.android.vending.billing.IMarketBillingService
	{
		private static final java.lang.String DESCRIPTOR = "com.android.vending.billing.IMarketBillingService";

		/** Construct the stub at attach it to the interface. */
		public Stub()
		{
			this.attachInterface(this, DESCRIPTOR);
		}

		/**
		 * Cast an IBinder object into an
		 * com.android.vending.billing.IMarketBillingService interface,
		 * generating a proxy if needed.
		 */
		public static com.android.vending.billing.IMarketBillingService asInterface(android.os.IBinder obj)
		{
			if((obj == null))
			{
				return null;
			}
			android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);

			// ここでリモート・クライアント間共通インターフェースで型チェックの後キャストしている。
			if(((iin != null) && (iin instanceof com.android.vending.billing.IMarketBillingService)))
			{
				return ((com.android.vending.billing.IMarketBillingService) iin);
			}
			return new com.android.vending.billing.IMarketBillingService.Stub.Proxy(obj);
		}

		public android.os.IBinder asBinder()
		{
			return this;
		}

		@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
		{
			switch(code)
			{
				case INTERFACE_TRANSACTION:
				{
					reply.writeString(DESCRIPTOR);
					return true;
				}
				case TRANSACTION_sendBillingRequest:
				{
					data.enforceInterface(DESCRIPTOR);
					android.os.Bundle _arg0;
					if((0 != data.readInt()))
					{
						_arg0 = android.os.Bundle.CREATOR.createFromParcel(data);
					}
					else
					{
						_arg0 = null;
					}
					android.os.Bundle _result = this.sendBillingRequest(_arg0);
					reply.writeNoException();
					if((_result != null))
					{
						reply.writeInt(1);
						_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
					}
					else
					{
						reply.writeInt(0);
					}
					return true;
				}
			}
			return super.onTransact(code, data, reply, flags);
		}

		private static class Proxy implements com.android.vending.billing.IMarketBillingService
		{
			private android.os.IBinder mRemote;

			Proxy(android.os.IBinder remote)
			{
				mRemote = remote;
			}

			public android.os.IBinder asBinder()
			{
				return mRemote;
			}

			public java.lang.String getInterfaceDescriptor()
			{
				return DESCRIPTOR;
			}

			/**
			 * Given the arguments in bundle form, returns a bundle for results.
			 */
			public android.os.Bundle sendBillingRequest(android.os.Bundle bundle) throws android.os.RemoteException
			{
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				android.os.Bundle _result;
				try
				{
					_data.writeInterfaceToken(DESCRIPTOR);
					if((bundle != null))
					{
						_data.writeInt(1);
						bundle.writeToParcel(_data, 0);
					}
					else
					{
						_data.writeInt(0);
					}
					mRemote.transact(Stub.TRANSACTION_sendBillingRequest, _data, _reply, 0);
					_reply.readException();
					if((0 != _reply.readInt()))
					{
						_result = android.os.Bundle.CREATOR.createFromParcel(_reply);
					}
					else
					{
						_result = null;
					}
				}
				finally
				{
					_reply.recycle();
					_data.recycle();
				}
				return _result;
			}
		}

		static final int TRANSACTION_sendBillingRequest = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
	}

	/** Given the arguments in bundle form, returns a bundle for results. */
	public android.os.Bundle sendBillingRequest(android.os.Bundle bundle) throws android.os.RemoteException;
}

難読化したときのclassファイルをリバースエンジニアリングしたとき,以下のとおり。

package a.a.a.a;

import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.Parcelable.Creator;

public abstract class b extends Binder
  implements a
{
  public static a a(IBinder paramIBinder)
  {
    Object localObject;
    if (paramIBinder == null)
      localObject = null;
    while (true)
    {
      return localObject;
      IInterface localIInterface = paramIBinder.queryLocalInterface("com.android.vending.billing.IMarketBillingService");

      // ここでリモート・クライアント間共通インターフェースで型チェックの後キャストしていたが難読化で
	  // クラス名が変更されている。
	  if ((localIInterface != null) && ((localIInterface instanceof a)))
        localObject = (a)localIInterface;
      else
        localObject = new c(paramIBinder);
    }
  }

  public boolean onTransact(int paramInt1, Parcel paramParcel1, Parcel paramParcel2, int paramInt2)
  {
    boolean bool;
    switch (paramInt1)
    {
    default:
    case 1598968902:
      for (bool = super.onTransact(paramInt1, paramParcel1, paramParcel2, paramInt2); ; bool = true)
      {
        return bool;
        paramParcel2.writeString("com.android.vending.billing.IMarketBillingService");
      }
    case 1:
    }
    paramParcel1.enforceInterface("com.android.vending.billing.IMarketBillingService");
    Bundle localBundle1;
    if (paramParcel1.readInt() != 0)
    {
      localBundle1 = (Bundle)Bundle.CREATOR.createFromParcel(paramParcel1);
      label81: Bundle localBundle2 = a(localBundle1);
      paramParcel2.writeNoException();
      if (localBundle2 == null)
        break label122;
      paramParcel2.writeInt(1);
      localBundle2.writeToParcel(paramParcel2, 1);
    }
    while (true)
    {
      bool = true;
      break;
      localBundle1 = null;
      break label81;
      label122: paramParcel2.writeInt(0);
    }
  }
}
ただ外部のサービスからクラス情報までロードされる気がしない。 開発環境でデバッグしてみると結局,難読化されていようがいまいが クラスはキャストされず代理クラスで実行されている。 googleドキュメント側の間違いじゃないか!?
関連記事
スポンサーサイト



コメントの投稿

非公開コメント

アクセス
あなたは
キーワード
カテゴリー
最近の記事
リンク
月別アーカイブ
ブロとも申請フォーム

この人とブロともになる

WEB検索
Google

RSSフィード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。