2010年10月8日金曜日

Android LVL を使う - Securing Android LVL Applications -

# 自分で予習しないと、くごー先生におこられそうなので、、、(嘘)

参考サイト

 ・Android Market licensing service

 ・Android Developers Blog: Securing Android LVL Applications

 ・Android Developers Blog: Proguard, Android, and the Licensing Server

 ・mokkouyouの開発日記 GDD2010Tokyoで聞いたのLVL関連のちょっと応用編な話

 ・LVL を使った Android アプリをセキュアにする

 ・Proguard, Android と ライセンシングサーバについて


Securing Android LVL Applications

LVL (License Verification Library : ライセンス検証ライブラリ) とは、アプリを認証外の使用から防ぐ強力な手段である Android Market licensing service の中核をなすものである。

ライセンスライブラリの導入だけでは端末にインストールされたアプリを完全にセキュアにはできない。そのため、アプリ自体に LVL の深い知識が必要とするような複雑さや異質さを LVL への追加する必要がある。


海賊行為(root を取った端末で apk を抜き出すとか)への対策として

 1. 難読化 - リバースエンジニアリングの難易度を上げる

 2. LVLの改造 - よくあるクラック手法を防ぐ

 3. 不正な改造の検知 - アプリに不正な改造が行われたことをすぐに検知

 4. ライセンスの検証 - ライセンスの検証を信頼済みのサーバに任せる



1. 難読化す
  ・ProGuardを使う
    ・android-proguard-commandline を使えば、
     コマンドラインからも可能


2. ライセンスライブラリ(LL)の改造
  逆アセンブリしたコードの改竄によるライセンス検証の通過を難しくする

  そのために、
   ・アプリのバイトコードをより複雑にする
   ・アプリ内でのLVLの実装を独自のものにする

  これにより、2種の攻撃を防ぐ
   ・ 自分のアプリケーションを改竄から防ぐ
   ・ 自分のアプリケーションが他のアプリケーション(もしくは
     オリジナルのLVL)で得た攻撃手法で突破されることを防ぐ


  LLを改造する対象の3分野
   1. LLロジックの中核部分
   2. LLの entry/exit ポイント
   3. 自分のアプリケーションでのLLの呼び出し方法、応答方法


  1. LLロジックの中核部分を改造する

   LVLのコアを構成する LicenseChecker class と LicenseValidator
   class をアプリケーションの元の機能が保たれる限り、様々な方法を
   使って可能な限りいじくりまわす

    例
     ・ switch文をif文に置き換える

     ・分岐には定数の代わりにXORやハッシュ関数を元に生成した値を使う

     ・ソースコードから使われてない部分を削る
      例えば、差し替え可能な Policy が必要でない場合は、Policy の
      インタフェース部分を削除して、LicenseValidator の残りの部分に
      Policy の検証部分をインライン実装する

     ・LVLをまるごと自分のアプリケーションパッケージ内に移動する

     ・ライセンス検証の別々の部分をそれぞれ別プロセスで検証する

     ・可能な限りインライン化する

    など

    *目標は自分のアプリケーションが独自のLVL実装を行うこと!
     サンプルコピペは厳禁です!


  2. LLの entry/exit ポイント

   攻撃者が同じ public interface を実装する偽のLVLを作成し、
   自分のアプリケーション内の関連するクラスを置き換えようとするやり方を防ぐ

   ・LicenseChecker のコンストラクタへ引数を追加することを検討する
   ・LicenseCheckerCallback のメソッド allow(), disallow() へ
    引数を追加することを検討する
   ・その都度生成した値を引数で渡すなど

     * 難読化ツールを使う場合、自分で allow(), disallow()
       をリネームしても難読化ツールが自動的にリネーム
       するので、特に効果はない


  3. 自分のアプリケーションでのLLの呼び出し方法、応答方法

   攻撃者が自分のアプリ内での LVL 呼び出し部分を調べ、
   攻撃する手法気をつける。

   例
     ライセンス検証失敗時に「終了」ボタンのついたダイアログを
     表示する場合、攻撃者がそのダイアログを表示する部分のコードを
     コメントアウトしたらどうなるか?
     それでもアプリケーションがきちんと終了できるように実装してる?

   これを避けるためには
     ・別のアクティビティを呼び出してライセンスが無効であることを表示し、
      直ちに元のアクティビティを終了する
     ・finish() メソッドが無効化されても機能するよう、
      別のところにもfinish()を仕込む
     ・タイムアウト後にアプリを終了するようなタイマーを仕掛ける

   他にも
     ・攻撃者はアプリ起動時のライセンスチェックを予想するだろうから、
      アプリの起動時間が数分経過してからライセンスチェックを行う

   など


   *Proguardなどのツールを使った場合でも、特定のメソッドは難読化
    されないことに注意する。重大な例として、onCreate() は
    リネームされない。
なぜなら、Androidシステムから
    呼び出し可能のままでなければならないから。ライセンスチェック部分を
    この部分に記述するのはいけない。攻撃者にバレバレです。


3. 不正な改造の検知

  アプリに耐タンパ性を持たせる

  攻撃者が LVL 部分を削除するためにコードを改ざんしようとした場合、
  このような改ざんを自分のコード内で検知することが可能

  実際に使える方法として、

   ・(わかりやすい方法)CRC32のような軽いハッシュ関数で自分の
    アプリケーションのコードのハッシュ値を作成し、前もって作成した
    正しい値とを比較する
   
    * 自分のアプリケーションのファイルのpathは
      context.GetApplicationInfo()で取得可能
    * 正しいチェックサムの値を記述しておくファイルをチェックサムの
      計算の対象に入れちゃだめ。
      (こういう情報を独自のサーバに置いておくのもあり)


   ・自分のアプリケーションがデバッグ可能かどうかを確認する

 
boolean isDebuggable = (0 != (
getApplcationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE
));


  

4. ライセンスの検証

  もし自分のアプリケーションに通信を行う部分が存在するのなら、
  自分のオンライン上のサーバに、ライセンスサーバの応答のコピー
  (ResponseData クラスにアプリ自身の署名とセットで格納されている)を
  送信するという強力な方法が使える

  サーバはユーザが認証済みか検証でき、検証に失敗した場合はオンラインの
  コンテンツの提供をすべて拒否できる

  ライセンスサーバからの応答は暗号化かつ署名されているので、
  自分のサーバでは Android マーケットパブリッシャーコンソールに格納された
  RSA 鍵を使ってライセンスの応答が改竄されていないかどうか確認できる


  サーバサイドでの検証を行う際は以下の項目を確認する

   ・応答の署名が正当なものか

   ・ライセンスサーバが応答で LICENSED を返したか

   ・パッケージ名とバージョンが正しいアプリケーションと同一か

   ・ライセンス応答が有効期限以内かどうか (VTライセンスextraを確認する)

   ・ライセンスサーバ内でライセンス確認におけるuserIdフィールドのログをとり、
    別のユーザに認証されたライセンス応答が再利用されていないか確かめる
    (特定のユーザのライセンス確認ばかりがありえないほど何回も要求される
     場合に疑っておくべきパターン)


  ライセンス応答をどうやって正当に検証するかについては
  LicenseValidator.verify().を参照すること

  ライセンス確認がサーバ内で行われている限りは(そしてそのサーバ自身が
  セキュアなら)、熟練したクラッカーでさえ回避できないという無上の価値を有する
  サーバが信頼されたコンピューティング環境にあることがその理由です。

  ユーザのコントロール下にあるコンピュータ(Android端末も含む)で
  実行されるコードは原則として信用してはいけない(ということを覚えておく)
  もしユーザにサーバサイドでのライセンス認証の失敗を知らせる場合、このような
  コードは勧告を告げる範囲内でのみ実行されなければいけない
  そして、未認証のユーザへのコンテンツの送信はなんであれ拒否すると言う動作を
  確実に行わなければならない

0 件のコメント:

コメントを投稿

'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter) {sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/'+code+'');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&');if(window.clipboardData) {window.clipboardData.setData('text',code);} else if(dp.sh.ClipboardSwf!=null) {var flashcopier=highlighter.flashCopier;if(flashcopier==null) {flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);} flashcopier.innerHTML='';} alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter) {var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('

'+highlighter.div.innerHTML+'

');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter) {var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter) {var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands) {var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter)) continue;div.innerHTML+=''+cmd.label+'';} return div;} dp.sh.Toolbar.Command=function(name,sender) {var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1) n=n.parentNode;if(n!=null) dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);} dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc) {var links=sourceDoc.getElementsByTagName('link');for(var i=0;i');} dp.sh.Utils.FixForBlogger=function(str) {return(dp.sh.isBloggerMode==true)?str.replace(/
|<br\s*\/?>/gi,''):str;} dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css) {this.value=value;this.index=index;this.length=value.length;this.css=css;} dp.sh.Highlighter=function() {this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;} dp.sh.Highlighter.SortCallback=function(m1,m2) {if(m1.indexm2.index) return 1;else {if(m1.lengthm2.length) return 1;} return 0;} dp.sh.Highlighter.prototype.CreateElement=function(name) {var result=document.createElement(name);result.highlighter=this;return result;} dp.sh.Highlighter.prototype.GetMatches=function(regex,css) {var index=0;var match=null;while((match=regex.exec(this.code))!=null) this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);} dp.sh.Highlighter.prototype.AddBit=function(str,css) {if(str==null||str.length==0) return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,' ');str=str.replace(/');if(css!=null) {if((/br/gi).test(str)) {var lines=str.split(' 
');for(var i=0;ic.index)&&(match.index/gi,'\n');var lines=html.split('\n');if(this.addControls==true) this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns) {var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150) {if(i%showEvery==0) {div.innerHTML+=i;i+=(i+'').length;} else {div.innerHTML+='·';i++;}} columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);} for(var i=0,lineIndex=this.firstLine;i0;i++) {if(Trim(lines[i]).length==0) continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0) min=Math.min(matches[0].length,min);} if(min>0) for(var i=0;i

Blogger Syntax Highliter

Version: {V}

http://www.dreamprojections.com/syntaxhighlighter

©2004-2007 Alex Gorbatchev.

'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter) {sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/'+code+'');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&');if(window.clipboardData) {window.clipboardData.setData('text',code);} else if(dp.sh.ClipboardSwf!=null) {var flashcopier=highlighter.flashCopier;if(flashcopier==null) {flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);} flashcopier.innerHTML='';} alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter) {var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('

'+highlighter.div.innerHTML+'

');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter) {var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter) {var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands) {var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter)) continue;div.innerHTML+=''+cmd.label+'';} return div;} dp.sh.Toolbar.Command=function(name,sender) {var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1) n=n.parentNode;if(n!=null) dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);} dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc) {var links=sourceDoc.getElementsByTagName('link');for(var i=0;i');} dp.sh.Utils.FixForBlogger=function(str) {return(dp.sh.isBloggerMode==true)?str.replace(/
|<br\s*\/?>/gi,'\n'):str;} dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css) {this.value=value;this.index=index;this.length=value.length;this.css=css;} dp.sh.Highlighter=function() {this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;} dp.sh.Highlighter.SortCallback=function(m1,m2) {if(m1.indexm2.index) return 1;else {if(m1.lengthm2.length) return 1;} return 0;} dp.sh.Highlighter.prototype.CreateElement=function(name) {var result=document.createElement(name);result.highlighter=this;return result;} dp.sh.Highlighter.prototype.GetMatches=function(regex,css) {var index=0;var match=null;while((match=regex.exec(this.code))!=null) this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);} dp.sh.Highlighter.prototype.AddBit=function(str,css) {if(str==null||str.length==0) return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,' ');str=str.replace(/');if(css!=null) {if((/br/gi).test(str)) {var lines=str.split(' 
');for(var i=0;ic.index)&&(match.index/gi,'\n');var lines=html.split('\n');if(this.addControls==true) this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns) {var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150) {if(i%showEvery==0) {div.innerHTML+=i;i+=(i+'').length;} else {div.innerHTML+='·';i++;}} columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);} for(var i=0,lineIndex=this.firstLine;i0;i++) {if(Trim(lines[i]).length==0) continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0) min=Math.min(matches[0].length,min);} if(min>0) for(var i=0;i

ページビューの合計