charles_log_nativesample_0222.chls

[Native ads 분석]

Native ads의 가장 큰 특징은 개발자로 하여금 Json(response format) 형태로 받은 ad response를 파싱하여 개발자가 원하는 방식/형태로 광고를 노출시킬 수 있다는 점이다. 


[contextCode]

contextCode is Native JS(java script) response and it needs to be loaded in a webview with functions called to fire impression and render beacon. If you add "trackertype":"url_ping"  parameter then you can receive the same data as Json format. Picture below contains contextCode object which means publisher does not add "trackertype":"url_ping"  parameter. 


server to server API 연동시, 매체사(publisher)의 서버에서 Json 형식으로 ad request를 전송해야하는데 이때, trackertype 객체를 생성하지 않거나 해당 객체의 value를 "url_ping"로 명시하지 않으면 java script 형태로 된 contextCode를 받게 된다. contextCode는 HTML 페이지를 포함하고 있는데 이를 다시 웹뷰에 로드, 적정한 함수를 호출하여 파싱하는 과정을 거쳐야 한다. 홈페이지에 나와있는 ad response 예시에sm는 contextCode가 없기 때문에 종종 "trackertype":"url_ping" 객체를 ad request에 실어 보내지 않은 매체사의 경우 잘못된 규격으로 ad response를 받았다고 생각하는 경우가 있다.


[Native ads packet captured]

Charles를 활용하여 어플리케이션(SDK) Ad-Network 서버(Inmobi) 간 ad request, ad response 패킷을 캡처해보았다.  
앱 실행과 동시에 API가 순차적 호출된다. 대략적은 구조/순서는 다음과 같다.


1) http://i.w.inmobi.com Request 부분

처음 http://i.w.inmobi.com.showad.asm로 POST. showad.asm의 Request가 ad request(광고 요청)이고 해당 요청에 대한 Response가 ad serve(광고 송출)이다. 


showad.asm의 request를 form탭에서 열어보면 다음과 같다.


sn과 sm 인자로 구분되어있다. sn은 무엇을 의미하는 지 모르겠지만 값이 1 이므로 간단한 헬스 체크 용도가 아닐까 예상한다. sm은 secure message를 의미하는 것 같다. 특정 알고리즘의로 decrypted 되어있다. 다음은 복호화 전후의 sm 본문이다.


1-1) Request sm 1차 복호화

https://docs.google.com/a/inmobi.com/document/d/1qX_Km8zbLRLEiJ3eoCh_6HVi5-vhRj904fxgaeEBQ3s/edit?usp=sharing


1-2) Request sm URL 2차 복호화 : percent-decryption

https://docs.google.com/a/inmobi.com/document/d/1KUxkMK52qvJEVnULq2oqHviHKOA6qwUrlui69I3KjW4/edit?usp=sharing


URL이기 때문에 parameter=value& 형태로 되어있다. 배너 사이즈, sdk version, orientation(가로/세로) 등의 정보를 포함하고 있는데 AIDL, IMID, GPID(advertising ID) 등 일부 파라미터는 암호화되어 있다. 일반적인 URL encoding 방식(percent-encoding)으로 암호화되어 있다. 복호화된 sm 본문을 다시 percent-decoding하면 다음과 같은 결과를 얻을 수 있다. 

(URL decryption site: http://meyerweb.com/eric/tools/dencoder/)


1-3) Request - sm URL 복호화 결과

https://docs.google.com/a/inmobi.com/document/d/1u_x3ctBV6M5BGheqYzRjIYtLC7l_Vodlrc8GX35URNA/edit?usp=sharing


AIDL파라미터를 자세히보면 어플리케이션이 설치된 디바이스의 advertising ID를 찾을 수 있다.

여기까지가 앱을 오픈하고 sdk가 InMobiSdk.init(MainActivity.this, property_ID) 함수를 호출한 후 ad-request를 보내는 과정 및 내용이다.

 

2) http://i.w.inmobi.com Response 부분

Response가 ad server(광고 송출)이다. Text 탭에서 해당 본문을 살펴보면 다음과 같이 나온다.


2-1) Response (복호화전)

https://docs.google.com/a/inmobi.com/document/d/1sd7BBg4fnQnARTYgSCCesIfM1Fuo7flXQ_lxgE2gMmg/edit?usp=sharing


2-2) Response (복호화 후)

https://docs.google.com/a/inmobi.com/document/d/1vmjv7ORcAnoETNSZhdxHXYMDCLPJMQrgpIxE9B2jDuA/edit?usp=sharing


인모비 홈페이지에 나와있는 Native ads와 비슷한 형태를 보인다. 1차 복호화된 ad response packet을 다시 복호화(Base64 encoded) 및 code alignment를 거치면 다음과 같은 형태가 된다.


{     "ads": [

    {

        "pubContent": "{\"title\":\"InMobi\",\"description\":\"Preview a selection of our mobile rich-media ads created by InMobi\\u0027s award winning creative services\",\"icon\":{\"width\":300,\"height\":300,\"url\":\"http://i.l.inmobicdn.net/uac-assets/com.inmobi.showcase-ANDROID-us-1250240w300h300\",\"aspectRatio\":1.0},\"landingURL\":\"https://play.google.com/store/apps/details?id\\u003dcom.inmobi.showcase\",\"cta\":\"Install\",\"rating\":3.0}"

        , "contextCode": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <meta name=\"viewport\" content=\"user-scalable=0, minimum-scale=1.0, maximum-scale=1.0\"/>\n    <style type=\"text/css\">\n        body {margin: 0; overflow: hidden;}\n    </style>\n    <link rel=\"dns-prefetch\" href=\"//i.l.inmobicdn.net\">\n</head>\n<body>\n<script type=\"text/javascript\" src=\"https://i.l.inmobicdn.net/sdk/sdk/500/android/mraid.js\"> </script>\n<div style=\"display:none; position:absolute;\" id=\"0clickTarget\"></div>\n    <div style=\"display:none; position:absolute;\" id=\"im_1855_clickTarget\"></div>\n<script type=\"text/javascript\">\n(function() {var d=_im_imai,e=window,g=encodeURIComponent,k=document,l=\'addEventListener\',m=\'prototype\',p=\'fireAdReady\',q=\'setAttribute\',r=\'getTime\',s=\'\',t=\'&\',v=\'0\',w=\'2\',x=\'=\',y=\'?\',z=\'SKStoreContentLoaded\',A=\'addReadyHandler\',B=\'clickCallback\',C=\'clickTarget\',D=\'disableAutoFireAdReady\',E=\'error\',F=\'fireAdFailed\',G=\'fireAdReady\',H=\'height\',I=\'iatSendClick\',J=\'iframe\',L=\'img\',M=\'impressionCallback\',N=\'loadSKStore\',O=\'m=\',P=\'openLandingPage\',Q=\'ready\',R=\'recordEvent\',S=\'seamless\',T=\'src\',U=\'width\';\ne.inmobi=e.inmobi||{};var V=e.inmobi;V.getTime=function(){return(new Date)[r]()};V.g=V.g||[];V.a=function(a,b){for(var c=V.g,f=0;f<c.length;f++)c[f].call(this,a,b)};V.recordEvent=V.a;V.b=!0;V.addReadyHandler=function(){d[l](Q,function(){V.b&&d[p]()})};V.fireAdReady=function(){d[p]()};V.fireAdFailed=function(){d.fireAdFailed()};V.disableAutoFireAdReady=function(){V.b=!1};\nfunction W(a){function b(a,b){c.a(a,b)}this.f=a.lp;this.e=a.ct;this.m=a.tc;this.o=a.bcu;this.d=a.ns;this.i=a.lpom;this.q=a.fb;this.p=a.fbom;this.b=!0;this.j=V[r]();this.l=0;this.n=a.sc;this.h=!1;this.c=mraid;imraid&&imraid.loadSKStore&&(this.c=imraid);a=this.d;var c=this;e[a+N]=function(){c.loadSKStore()};e[a+P]=function(){X(c);if(c.h)c.h=!1,c.c.showSKStore(c.f);else{var a=Y(c,c.f);W.k[c.i](a)}};e[a+B]=function(){Z(c)};e[a+M]=function(){$(c)};e[a+R]=function(a,b){c.a(a,b)};e[a+G]=function(){c[p]()};\ne[a+F]=function(){c.fireAdFailed()};e[a+A]=function(){V.addReadyHandler()};e[a+D]=function(){V.b=!1};e[a+R]=b;this.n&&V.g.push(b)}V.Imai=W;W.k={3:d.openExternal,4:d.openEmbedded,5:d.ios.openItunesProductView};function Y(a,b){return b.replace(/\\$TS/g,s+V[r]()).replace(/\\$LTS/g,s+a.j).replace(/\\$STS/g,s+a.l)}W[m].loadSKStore=function(){if(5==this.i){var a=this;a.c[l](z,function(){a.h=!0});mraid[l](Q,function(){a.c.loadSKStore(a.f)})}};\nfunction X(a){var b=!1;d[l](E,function(){b||(b=!0,a.a(103),W.k[a.p](a.q))})}function Z(a){d.onUserInteraction();var b=e[a.d+I];b&&b();if(null!=a.e)for(var b=a.e.length,c=0;c<b;c++)d.pingInWebView(Y(a,a.e[c]))}function $(a){a.l=V[r]();if(null!=a.m)try{var b=k.getElementById(a.d+C),c=a.m,f=k.createElement(J);f[q](S,S);f[q](H,v);f[q](U,w);b.appendChild(f);var h=f.contentWindow;h&&(h.document.write(c),h.document.close())}catch(u){}}\nW[m].a=function(a,b){var c=this,f=y,h=this.o;0<=h.indexOf(y)&&(f=t);h+=f+O+a;if(b)for(var u in b)h+=t+g(u)+x+g(b[u]);1==a&&(this.j=V[r]());if(3==a||4==a)d.ping(h,!0);else{var K=function(a,b,f){if(!(0>=f)){var h=k.getElementById(c.d+C),n=k.createElement(L);n[q](T,a);n[q](H,v);n[q](U,w);void 0!=n[l]&&n[l](E,function(){e.setTimeout(function(){3E5<b&&(b=3E5);K(a,2*b,f-1)},b*Math.random())},!1);h.appendChild(n)}};K(h,1E3,5)}18==a&&$(this);8==a&&Z(this)};W[m].fireAdReady=function(){d[p]()};\nW[m].fireAdFailed=function(){d.fireAdFailed()};})();\n (function() {var a=window,c=\'handleClick\',e=\'handleTouchEnd\',f=\'handleTouchStart\';a.inmobi=a.inmobi||{};function g(b,h){this.b=h;this.a=this.c=!1;var d=this;a[b+c]=function(){d.click()};a[b+f]=function(){d.start(a.event)};a[b+e]=function(){d.end()}}a.inmobi.OldTap=g;g.prototype.click=function(){this.c||this.b()};g.prototype.start=function(b){this.a=this.c=!0;b&&b.preventDefault()};g.prototype.end=function(){this.a&&(this.a=!1,this.b())};})();\n\nnew window.inmobi.Imai({\"bcu\":\"http://au.w.inmobi.com/c.asm/C/-1/e7i1how1lv/1kqk/w/64/m1/u/0/0/0/-1/644a7169-015a-1000-e958-01440b380091/a610/0/-1/0/1/x/-1/NW/-1/0/sdk/6.1.0/dir/Y29tLmlubW9iaS5uYXRpdmVhZC5zYW1wbGU~/BCoCOCV1bXAtcHJvZDEwMTFfYWRzX2hrZzFfc3VwcGx5LXVtcF9wcm9kAA\\u003d\\u003d/-1/AA\\u003d\\u003d/7/a153f8ec\",\"ns\":\"0\",\"lpom\":0,\"fbom\":0,\"sc\":true});\n        new window.inmobi.Imai({\"lp\":\"https://play.google.com/store/apps/details?id\\u003dcom.inmobi.showcase\",\"ct\":[\"http://c.w.inmobi.com/c.asm/C/b/e7i1how1lv/1kqk/w/64/m1/u/0/0/0/eyJVSUQiOiI0M2U3Yzk2OC00NjllLTQyZmYtOGMyOS03NGRmYTU3YWFlYTkiLCJHUElEIjoiNDNlN2M5NjgtNDY5ZS00MmZmLThjMjktNzRkZmE1N2FhZWE5In0~/644a7169-015a-1000-e9f8-0145495b0091/a610/0/-1/1/1/x/11029967/NW/2t/0/sdk/6.1.0/dir/Y29tLmlubW9iaS5uYXRpdmVhZC5zYW1wbGU~/NtKvg4P-U6g4WTVtd2NlaHpZQ25EY1ZaS2ZMN3VGVThPZ2FJS0hLR0dlbFdVWndFQm5pa2NTWXBSOUk1M2V3PT0YBk5BVElWRRw8Jsq9kci6uci2AiIAADnFoJkCwK0Bgn3I4gHwqAIQsD_SiQLUhgO2rQHcnAG-PiJIJXVtcC1wcm9kMTAxMV9hZHNfaGtnMV9zdXBwbHktdW1wX3Byb2QiAA\\u003d\\u003d/0/AA\\u003d\\u003d/7/b61301f?at\\u003d2\\u0026am\\u003d1\\u0026ct\\u003d$TS\\u0026lt\\u003d$LTS\\u0026st\\u003d$STS\"],\"bcu\":\"http://et.w.inmobi.com/c.asm/C/b/e7i1how1lv/1kqk/w/64/m1/u/0/0/0/eyJVSUQiOiI0M2U3Yzk2OC00NjllLTQyZmYtOGMyOS03NGRmYTU3YWFlYTkiLCJHUElEIjoiNDNlN2M5NjgtNDY5ZS00MmZmLThjMjktNzRkZmE1N2FhZWE5In0~/644a7169-015a-1000-e9f8-0145495b0091/a610/0/-1/0/1/x/11029967/NW/2t/0/sdk/6.1.0/dir/Y29tLmlubW9iaS5uYXRpdmVhZC5zYW1wbGU~/NtKvg4P-U6g4WTVtd2NlaHpZQ25EY1ZaS2ZMN3VGVThPZ2FJS0hLR0dlbFdVWndFQm5pa2NTWXBSOUk1M2V3PT0YBk5BVElWRRw8Jsq9kci6uci2AiIAADnFoJkCwK0Bgn3I4gHwqAIQsD_SiQLUhgO2rQHcnAG-PiJIJXVtcC1wcm9kMTAxMV9hZHNfaGtnMV9zdXBwbHktdW1wX3Byb2QiAA\\u003d\\u003d/0/AA\\u003d\\u003d/7/e719aaae\",\"ns\":\"im_1855_\",\"lpom\":3,\"fbom\":3,\"sc\":true});\n    new window.inmobi.OldTap(\"im_1855_\", function() {window[\'im_1855_openLandingPage\'](); window[\'im_1855_recordEvent\'](8);});\n    window[\'im_1855_loadSKStore\']();\n</script></body>\n</html>\n"

        , "namespace": "im_1855_"

        , "markupType": "pubJson"

        , "expiry": 3300

        , "metaInfo":

        {

            "creativeType": "unknown"

            , "iasEnabled": false

        }

    }]

    , "requestId": "644a7169-015a-1000-e900-014000000090"

}

    

위 코드에서 결국 개발자들이 파싱해야하는 부분이 "pubContent" 와 "contextCode" 두 부분인데 각각의 코드를 다시 code alignment하면 다음과 같이 된다.

pubContent(javascript)

※ 주의할 점이 있었는데 pubContent의 경우 escape character + unicode가 있어서 javascript code alignment에서 복호화 및 code alignment가 힘들었다. 위의 예제에서는 1) escape character \ + unicode \u0027(APOSTROPHE), 2) escape character \ + unicode \u003d(EQUALS SIGN)이 포함되어 있다.


아래의 사이트에 들어가서 https://r12a.github.io/apps/conversion/ Convert \x의 체크박스에 check한 후 돌려주면 유니코드로 변환해준다. \x indicates a hexadecimal character escape. 


contextCode(HTML)


그러면 이제 Inmobi SDK 예제에 있는 parsing 부분에서 자료형을 왜 Json 객체로 cast하는지 알 수 있다.



+ Recent posts