소곤소곤 ad

2014년 5월 12일 월요일

안드로이드 저장공간이 부족합니다 메세지가 나올때...

앱업데이트시에 저장공간이 부족합니다.... 라고 나오는 경우

폰 저장공간이 충분히 여유가 있는 경우에도 이런 메세지가 나오는 경우 대부분은 다른앱들의 쓰레기가 내부저장공간을 차지하고 있어서인데, 사용자들에게 안쓰는앱/폴더/파일들좀 삭제하면 된다고 알려줘봐야 무슨말인지 모르는 경우가 많다.

차라리 우리앱이 다른 널찍한 곳을 찾아나서는게 나을수도 있는데, Manifest에서 ndroid:installLocation = auto 옵션을 이용하면 된다.

친절한 설명 : http://aroundck.tistory.com/139
스마트폰 저장소 정리만 잘해줘도 충분 : http://comterman.tistory.com/696

2014년 5월 6일 화요일

구글 인앱결제 크랙 / 프리덤 막기 / Simplest way to detect Freedom

Simplest way to detect Freedom

구글 인앱결제 크랙(프리덤등)을 막는 아주 간단한 방법 하나는 구매번호(Order ID) 의 형태를 확인해 보는것이다.

정상예 : 12999xxxxxxxx4705758.13175xxxxxxxx825
(내 경우는 길이가 37자리 = 숫자36개+쩜)
가짜예 : 4141975266280705695

이정도 포맷과 길이도 못 맞춰서 보내는 넘들이 무슨 해킹을 한다고...

Here is an easy way to verify IAP result. Just check the format of 'order id' within the result json string out.  Abnormal trial has abnormal format.

2014년 2월 18일 화요일

구글플레이 안드로이드 인앱결제 보안 이야기 #1 (프리덤을 막자)

(1) public key를 숨겨라

※ 서버검증할거라면 클라이언트(앱)에서 public_key는 필요가 없다.  => 자세한내용보기


/* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY
 * (that you got from the Google Play developer console). This is not your
 * developer public key, it's the *app-specific* public key.
 *
 * Instead of just storing the entire literal string here embedded in the
 * program,  construct the key at runtime from pieces or
 * use bit manipulation (for example, XOR with some other string) to hide
 * the actual key.  The key itself is not secret information, but we don't
 * want to make it easy for an attacker to replace the public key with one
 * of their own and then fake messages from the server.
 */
String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";

인앱기능을 구현하기위해 샘플 코드들을 가져다 놓고서 열어보면 이런 부분이 있다.

위의 /* 코멘트 */ 부분은 눈에 잘 들어오지 않고 (영어라서!) "Your key PLACE_IT_HERE"라는 문구가 보인다. 얼른 복사해서 붙여넣고 넘어가려는게 일단 문제다.

그냥 붙여넣지 말자. Construct를 해서 넣어야 한다. 한번쯤은 꼬아 놓자.

String piece1 = "SDFGJKGB4UIH234WE/FRT23RS#trashdata#DF/3DFUISDFVWE";
String piece2 = "SDFGJKGB4UIHUI#trashdata#SDFVWE";
String piece3 = "BDY#trashdata#ASGBDNAWGRET24IYE23das4saGBENWKD";
String piece4 = "#trashdata#432423SDF23R/+SDDS";

mHelper = new IabHelper(this, piece1.substring(...) + piece2.substring(...) + piece3.substring(...) + piece4.substring(...));
이런식으로만 해 두어도 난이도가 올라간다. 크래커입장에서는 이 키를 자기자신의 키로 변경하고자 시도할텐데, 일반 문자열로 들어가 있으면 식은죽 먹기일테지.

권장하는 방법은 실시간으로 서버로부터 이 키를 암호화된채로 받아서 복호화해서 IabHelper를 초기화하는 것.


(2) 결제과정에서 구글지갑을 꼭 체크하자.


유저가 결제한 직후에,  launchPurchaseFlow -> QueryInventory -> Consume -> onFinishedConsume 의 절차를 거치도록 하자.

*) 결제후에, 인벤토리에 그 아이템이 있는지 검사하고, 컨슘(사용)한다음에 최종적으로 OK가 나왔을때에 서버측에 전달하여 유료아이템 구매를 처리하는 식.

구글에서 제공해주는 샘플소스를 잘 살펴보자.


(3) 구매완료후 영수증을 확인한다. (Clinet Side) 

※ 영수증을 서버에서 검증한다면 굳이 검증할 필요는 없다.



public void onConsumeFinished(Purchase purchase, IabResult result) {
    if (result.isSuccess()) {
       if(purchase != null){
           // provision the in-app purchase to the user
           verifyBilling(purchase.getSignature(), purchase.getOriginalJson());
        } else {
           LogToServer("onConsumeFinished:purchase=null");
        }
   } else {
        LogToServer("onConsumeFinished:consume=failed");
   }
}

your_publickey, json_data, signature로 검증을 할 수 있다.  (Security.Java에 잘 구현되어 있다)


(4) 서버측에서 검증한다 (Server Side)


영수증데이터를 서버로 보내 서버에서 검증하는 방법이다.

인터넷을 뒤져서 c++로 구현된 서버측 검증소스를 하나 찾아냈는데, 잘 동작하지 않는다. 게다가 이렇게 뭐가뭔지 모를 소스코드는 기겁하게 만든다.

int InappBillingVerify(const char* data, const char* signature, const char* pub_key_id)
{
    std::shared_ptr<EVP_MD_CTX> mdctx =
    std::shared_ptr<EVP_MD_CTX>(EVP_MD_CTX_create(), EVP_MD_CTX_destroy);
    const EVP_MD* md = EVP_get_digestbyname("SHA1");

    EVP_VerifyInit_ex(mdctx.get(), md, NULL);

    EVP_VerifyUpdate(mdctx.get(), (void*)data, strlen(data));

    std::shared_ptr<BIO> b64 = std::shared_ptr<BIO>(BIO_new(BIO_f_base64()), BIO_free);
    BIO_set_flags(b64.get(),BIO_FLAGS_BASE64_NO_NL);

    std::shared_ptr<BIO> bPubKey = std::shared_ptr<BIO>(BIO_new(BIO_s_mem()), BIO_free);
    BIO_puts(bPubKey.get(),pub_key_id);
    BIO_push(b64.get(), bPubKey.get());
    std::shared_ptr<EVP_PKEY> pubkey = 
             std::shared_ptr<EVP_PKEY>(d2i_PUBKEY_bio(b64.get(), NULL), EVP_PKEY_free);
    std::string decoded_signature = Base64Decode(std::string(signature));

    return EVP_VerifyFinal(mdctx.get(), (unsigned char*)decoded_signature.c_str(), 

             decoded_signature.length(), pubkey.get());
}

출처 : http://kukuta.tistory.com/166

이 분의 소스를 가지고 구현해보았지만 에러가 나면서 실패. 소스코드를 보고 있으면 머리가 지끈지끈하다.

20년을 프로그래밍을 해왔는데, 이 소스는 대체 뭐란 말이냐.... 이유없이 Segmentation Fault 가 나오거나 에러코드 -1이 떨어지는데, 왜 안되는지 이유조차 알 수가 없고, 직접좀 만들어볼까 하니 외계인이 만들어 놓은것 같은 openssl 함수들과 구조체들을 보면 게보린 한통은 필요해 보인다.

결국 포기 T.T

불행한 것은 전 세계를 다 뒤졌음에도, 아직 잘 동작하는 c++소스를 못 찾았았다는 것. 결국 임시방편으로 내가 쓴 방법은 이렇다.

1) Security.Java의 verify를 호출하여 검증하는 Java프로그램을 만든후에, 실행가능한 jar로 만든다.
2) 게임서버에서 java를 호출하여 실행시킨후에 출력값을 얻어온다. (리눅스 pipe를 이용한다)

*. 이 과정에서 jar가 정상 실행되지 않거나 ClassNotFound등의 오류에 시달려야 했는데, 패키지이름을 부여하지 않아서라든지 jdk 버전이 이클립스개발용과 서버용이 달라서 생기는 문제들.


(4-1) 구글 개발자 API를 이용.

구글은 "판매자의 지갑"을 들여다 볼 수 있는 API를 제공한다.  하지만 구현이 만만치 않다. 게다가 c++ 버전의 샘플이나 라이브러리는 제공하지도 않는다.

느리고, 하루 20만회 호출로 제한되어 있으며, 가끔 응답없음이 나오기도 한다그래서 일단 패쓰.


(*) 프리덤!

구글 인앱결제를 우회하게 해주는 프리덤이라는 앱이 있는데,  (프리덤, freedom, ㅍㄹㄷ 으로 검색) 가짜 카드로 무한 결제가 가능한 무시무시한 앱이다.
4단계의 서버검증에서 걸러낼 수 있다. 그러니까, 인앱결제를 넣을거라면 서버검증은 필수라는 것.