소곤소곤 ad

2015년 7월 2일 목요일

프리덤 인앱결제 크랙 막기 (서버없이)

프리덤으로 인앱 결제를 하는 것을 막기위해, 일반적으로 서버를 두고 별도 검증을 하는 것이 필수적인데, 왜 그래야 할까?
인앱결제과정에서 가짜영수증과 가짜서명이 자체 검증(Secure.verify)을 문제없이 통과하게 되는데, 프리덤에서 이 부분을 무조건 true로 리턴하는 패치를 삽입하는 듯하다. 
java.security.Signature.verify()를 따라가다 보면, abstract protected로 설정된 engineVerify 함수를 만나게 되는데, 이 부분이 굉장히 의심스럽다(고 한다).
그래서, 이 부분을 직접 구현하는 시도가 있었고 아직까지는 성공적으로 동작한다(고 한다).

원문 (http://stackoverflow.com/questions/21966369/protecting-in-app-purchases-from-freedom-hack) 을 살펴보면,

The current implementation of freedom is that it will replace (redirect) all the method calls of java.security.Signature.verify(byte[]) to a freedom's jni method which in turn just simply always return true (or 1).
Take a look at java.security.Signature.verify(byte[]):
 public final boolean verify(byte[] signature) throws SignatureException {
        if (state != VERIFY) {
            throw new SignatureException("Signature object is not initialized properly");
        }
        return engineVerify(signature);
    }
Here the engineVerify method is an abstract protected method which is first defined in java.security.SignatureSpi(Signature extends SignatureSpi). OK, that enough, because I can't believe java.security.Signature.verify(byte[]) method anymore, I would use engineVerifymethod directly. To do that, we need to use reflection. Modify the verify method of IABUtil/Security from:
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
        Signature sig;
        try {
            sig = Signature.getInstance(SIGNATURE_ALGORITHM);
            sig.initVerify(publicKey);
            sig.update(signedData.getBytes());
            if (!sig.verify(Base64.decode(signature))) {
                Log.e(TAG, "Signature verification failed.");
                return false;
            }
            return true;
        } catch (...) {
            ...
        }
        return false;
    }
To:
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
        Signature sig;
        try {
            sig = Signature.getInstance(SIGNATURE_ALGORITHM);
            sig.initVerify(publicKey);
            sig.update(signedData.getBytes());
            Method verify = java.security.SignatureSpi.class.getDeclaredMethod("engineVerify", byte[].class);
            verify.setAccessible(true);
            Object returnValue = verify.invoke(sig, Base64.decode(signature));
            if (!(Boolean)returnValue) {
                Log.e(TAG, "Signature verification failed.");
                return false;
            }
            return true;
        } catch (...) {
            ...
        }
        return false;
    }
That is simple but it works with the current implementation of freedom until they update its algorithm in the future. (BTW, if you want to test if this works or not, this is my simple app).


즉, Security의 verify를 위 코드처럼 바꾸면, 프리덤이 바꿔치기한 (무조건 true를 리턴해주는) engineVerify를 무시하고 직접 검증할 수 있다. 
서버를 두지 않고, 인앱결제를 검증하는 경우에는 아주 간단하고 유용한 방법이다. 실제로 프리덤을 막아낼까? 이 코드를 작성한 사람이 직접적용한 앱을 가지고 테스트 해보자. https://play.google.com/store/apps/details?id=com.rainbowedu.toeicguru 



2015년 6월 26일 금요일

webview에서 새창으로 브라우저 열기, Opening new browser from webview

Webview를 이용하여 웹페이지를 앱내부에 넣는 것은 그리 어렵지 않다. 그런데, 보다 복잡한 곳으로의 링크를 webview아닌 브라우저를 이용해서 하도록 하고 싶을 때가 있다.

또, 내 경우는 웹뷰내에 Adsense 구글광고가 들어 있는데 이용자가 이 것을 클릭했을때에 처리가 되지 않는 문제가 있어서 별도 브라우저를 구동시키는 방법이 필요했다.


 mWebView.setWebViewClient (
  new WebViewClient () {
   @Override        
   public boolean shouldOverrideUrlLoading (WebView view, String url) 
   {             
    view.loadUrl(url);             
    return true;
   }
  }
 );


shoulOverrideUrlLoading 에서 url 이 내 페이지가 아닌 경우 브라우저를 실행시키면 될거 같았는데, 안타깝게도 이 아이디어는 동작하지 않는다.

stackoverflow.com 에서 찾아낸 아이디어는, ChromeClinet를 이용해서 새로운 WebView를 만든다음에, 그 안쪽의 shoulOverrideUrlLoading 에서 브라우저를 구동시키는 것이다. 이게 무슨 복잡한 방법이냐 싶은데...  잘 돌아가니 OK!


 WebSettings webSetting = mWebView.getSettings();

 webSetting.setJavaScriptEnabled(true); 
 webSetting.setJavaScriptCanOpenWindowsAutomatically(true);
 webSetting.setSupportMultipleWindows(true);


 mWebView.setWebChromeClient(new WebChromeClient() {
         @Override 
         public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg)
         {
          // return true or false after performing the URL request
          WebView newWebView = new WebView(MyActivity.this);
                view.addView(newWebView);
                WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
                transport.setWebView(newWebView);
                resultMsg.sendToTarget();

                newWebView.setWebViewClient(new WebViewClient() {
                    @Override
                    public boolean shouldOverrideUrlLoading(WebView view, String url) {
                        Intent browserIntent = new Intent(Intent.ACTION_VIEW);
                        browserIntent.setData(Uri.parse(url));
                        startActivity(browserIntent);
                        return true;
                    }
                });          
          return true;
         }
        });

        mWebView.loadUrl ("...");

2015년 6월 10일 수요일

Display the Loading Dialog for android system 안드로이드 시스템 로딩 아이콘 표시하기

앱을 만들다보면, 어떤 오래걸리는 작업중에 로딩을 표시하고픈 일이 자주 있는데, 이미지를 그리고 레이아웃을 잡고 애니메이션을 시키고 하는 일을 직접 다 하려고 보면 엄두가 안난다.

뭔가 쉽게 로딩화면을 표시해주는 방법이 없을까? 바로 ProgressDialog를 사용하는 것이다. 한 줄이면 끝난다.

ProgressDialog dialog = ProgressDialog.show(context, "", "Loading. Please wait...", true);





없어지게 하고 싶으면, dialog.dismiss(); 끝.