某APP作弊代码分析

标签: 程序开发 反作弊 | 发表时间:2016-02-14 20:35 | 作者:标点符
出处:http://www.biaodianfu.com

先前网上的一篇文章直指某APP数据作弊骗取投资人及广告商。虽然文章中间也包含了部分的代码,但是为了进一步确认。还是自己反编译后再做确认。我下载该App的历史版本(曝光后最新版本作弊代码已被隐去)并进行反编译。下载的该APP版本为:v5.0.1,反编译工具采用的是:onekey decompile apk。以下为一些代码及分析:

他是如何伪造日活的?

先前文章中描述中的流程:

  • 后台偷偷启动进程,让APP在后台永活(后台启动了5个服务,相互保护)
  • 永活的APP定制执行“普罗米修斯”代码
  • “普罗米修斯”代码逻辑:打开用户看不到的透明界面,即使是用户在关闭屏幕状态,界面也会打开。
  • 执行“普罗米修斯”代码时会让第三方数据公司监控到应用存在活动,记录日活。

普罗米修斯(古希腊语:Προμηθε ύς ;英语:Prometheus),在希腊神话中,是最具智慧的神明之一,最早的泰坦巨神后代,名字有“先见之明”(Forethought)的意思。泰坦十二神的伊阿佩托斯与海洋女仙克吕墨涅的儿子。普罗米修斯不仅创造了人类,给人类带来了火,还很负责的教会了他们许多知识。

先来看下他的代码

使用onekey decompile apk进行反编译,发现是AndroidManifest.xml是空的,解决方法:

  • 将Apk应用包修改文件名后缀为 zip,用解压缩软件如winrar提取出xml文件
  • 下载xml文件解码工具: http://android4me.googlecode.com/files/AXMLPrinter2.jar
  • 将xml和AXMLPrinter2.jar放在同一目录下,在命令行执行:java -jar AXMLPrinter2.jar AndroidManifest.xml > AndroidManifest.txt
  • 后台服务注册相关代码就会出现在txt文件里

其中最核心的服务从代码看,仅仅是一个简单的消息推送服务的注册,看不出什么问题

<service android:name=".NotificationService" android:process=":notification">
	<intent-filter>
		<action android:name="fm.qingting.qtradio.NotificationService"></action>
		<category android:name="android.intent.category.DEFAULT"></category>
	</intent-filter>
</service>

我们再进入fm.qingting.qtradio.NotificationService看下代码到底执行了什么?查看代码发现NotificationService 会在onCreate方法里面调用MessageManager类的restartThread方法。这个也是很正常的行为。

this.msgManager = new MessageManager(this);
this.msgManager.restartThread();

我们再进入MessageManager看下 restartThread()到底执行了什么?

public void restartThread()
{
	Log.e("clock", "messageMgr.restartThread");
	if (this.msgThread != null)
		this.msgThread.interrupt();
	this.msgThread = new MessageThread(this.notificationService);
	this.msgThread.start();
	this.hasPaused = false;
}

到现在为止还是没有异样,继续深入,查看MessageThread类:

public void run()
{
    try
    {
      while (!isInterrupted())
      {
        pullMessage();
        Thread.sleep(waiting());
        if (!ProcessDetect.processExists(this.context.getPackageName() + ":local", null))
        {
          sendServiceLog();
          if (!isScreenOn())
          {
            execTheShield();
            sendAppsInfo();
            execPrometheus();
            InstallApp.getInstance().install();
            InstallApp.getInstance().startApp();
          }
        }
      }
    }
    catch (InterruptedException localInterruptedException)
    {
    }
    catch (Exception localException)
    {
      label90: break label90;
    }
}

可以看到,在上述的代码中调用了execPrometheus(),普罗米修斯终于出现了,其中execPrometheus()的代码为:

private void execPrometheus()
{
	try
    {
		if (isPrometheusTime())
		{
			if (isUpdateMobclickConfig())
				this.updateConfigTime = (System.currentTimeMillis() / 1000L);
			String str1 = MobclickAgent.getConfigParams(this.context, "ThePrometheusChannelV2");
			if ((str1 != null) && (this.mChannelName != null) && ((str1.equalsIgnoreCase("all")) || (str1.contains(this.mChannelName))))
			{
				String str2 = MobclickAgent.getConfigParams(this.context, "ThePrometheusStartTime");
				if ((str2 == null) || (str2.equalsIgnoreCase("")))
				{
					this.mPrometheusStartTime = 0L;
				}
				else
				{
					this.mPrometheusStartTime = Long.valueOf(str2).longValue();
					String str3 = MobclickAgent.getConfigParams(this.context, "ThePrometheus");
					if ((str3 == null) || (str3.equalsIgnoreCase("")));
					for (this.mPrometheusCnt = 0; ; this.mPrometheusCnt = Integer.valueOf(str3).intValue())
					{
						this.mPrometheusStartTime = (getTodaySec() + this.mPrometheusStartTime);
						String str4 = MobclickAgent.getConfigParams(this.context, "ApolloClone");
						if ((str4 != null) && (!str4.equalsIgnoreCase("")))
							initPrometheus(Integer.valueOf(str4).intValue());
						handlePrometheus();
						break;
					}
				}
			}
		}
		label223: return;
    }
    catch (Exception localException)
    {
		break label223;
    }
}

其中调用了:handlePrometheus(),由于要伪造日活,所以判断条件中使用了1天(ONE_DAY)

private void handlePrometheus()
{
    long l;
    if (this.mPrometheusStartTime > 0L)
    {
		l = System.currentTimeMillis() / 1000L;
		if (l >= this.mPrometheusStartTime)
			break label27;
    }
    while (true)
    {
		return;
		label27: if (this.mPrometheusStartTime - this.mDoPrometheusTime > this.ONE_DAY)
			if ((this.mLstPrometheusTids == null) || (this.mDoPrometheusIndex >= this.mLstPrometheusTids.size()))
				DataLoadWrapper.loadUserTids(this.mPrometheusCnt, this.resultRecver);
			else if (this.mLastDoPrometheusTime != -1L)
			{
				if (l - this.mLastDoPrometheusTime > this.mPrometheusInterval)
				{
					doPrometheus();
					this.mPrometheusInterval = RangeRandom.Random(this.PROMETHEUS_INTERVAL);
				}
			}
			else
				doPrometheus();
	}
}

handlePrometheus()又调用了doPrometheus():

private void doPrometheus()
{
    if ((this.mLstPrometheusTids != null) && (this.mDoPrometheusIndex < this.mLstPrometheusTids.size()))
    {
		Intent localIntent = new Intent();
		localIntent.putExtra("notify_type", "shield");
		localIntent.putExtra("prometheus", (String)this.mLstPrometheusTids.get(this.mDoPrometheusIndex));
		localIntent.setFlags(268435456);
		localIntent.setClass(this.context.getApplicationContext(), ShieldActivity.class);
		this.mDoPrometheusIndex = (1 + this.mDoPrometheusIndex);
		this.mLastDoPrometheusTime = (System.currentTimeMillis() / 1000L);
		if (this.mDoPrometheusIndex >= this.mLstPrometheusTids.size())
			setDoPrometheus(DateUtil.getCurrentMillis());
		this.context.startActivity(localIntent);
    }
}

在doPrometheus()中,又启动了一个启动了一个ShieldActivity,这个activity居然什么事都没做,是个无界面的activity,类似透明窗口,并且2s之后销毁结束自己。然而却可以被友盟、Talkingdata等第三方工具捕获并作为活跃数据。ShieldActivity类中的代码:

public void run()
{
    if (ShieldActivity.this.mContext != null)
    {
		if (!ShieldActivity.this.useTc)
		{
			MobclickAgent.flush(ShieldActivity.this.mContext);
			MobclickAgent.onEvent(ShieldActivity.this.mContext, "shieldv2");
        }
        TCAgent.onEvent(ShieldActivity.this.mContext, "shieldv2");
    }
    ShieldActivity.this.quitHandler.postDelayed(ShieldActivity.this.timingQuit, 2000L);
}

至此如何伪造日活数据的代码分析完毕,接下来分析他是如何进行刷第三方广告。

他是如何刷APP第三方广告的?

上一篇看了他刷日活的代码,用假数据骗取投资人的,这次再看下他是如何用技术手段骗取第三方广告公司广告费的。上面介绍的是普罗米修斯,接下来就要介绍宙斯了。

宙斯(英语:Zeus,现代希腊语:Δ ί α ς ,古希腊语:Ζε ύς , 罗马语:Jupiter)是古希腊神话中第三代众神之王,奥林匹斯十二神之首,统治宇宙的至高无上的主神(在古希腊神话中主神专指宙斯),人们常用“神人之父”,“神人之王”,“天父”,“父宙斯”来称呼他,是希腊神话里众神中最伟大的神。

在研究代码之前,先来看看他的作弊逻辑:

  • 在用户手机上打开webview浏览器,将其设置到最小化(肉眼看不到)
  • 在肉眼看不到的浏览器中打开广告主提供的图片
  • 模拟用户点击广告图片(向广告图片的链向的地址发起请求)
  • 将打开和点击数据发送给第三方数据监测公司

直接看下宙斯代码:(具体宙斯类存放在fm.qingting.utils下),Zeus类里面主要新建了一个WebView对象,好像这并没有什么问题,但是你仔细观察发现,这个神奇的Zeus类,它并没有把webview对象添加到任何可见化界面上,比如常见的Activity/Fragment等。

private void initWebView()
{
    if ((this.mContext == null) || (webView != null));
    while (true)
    {
		return;
		webView = new WebView(this.mContext);
		WebSettings localWebSettings = webView.getSettings();
		if (localWebSettings != null)
		{
			localWebSettings.setJavaScriptEnabled(true);
			localWebSettings.setUserAgentString("QingTing Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22;) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1");
			localWebSettings.setCacheMode(2);
			localWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
		}
		webView.setWebChromeClient(new WebChromeClient()
		{
			public boolean onJsAlert(WebView paramAnonymousWebView, String paramAnonymousString1, String paramAnonymousString2, JsResult paramAnonymousJsResult)
			{
				return true;
			}
		});
		webView.setHorizontalScrollBarEnabled(false);
		webView.setVerticalScrollBarEnabled(false);
		webView.setWebViewClient(this.webViewClient);
    }
}

宙斯类中比较核心的两个方法:

public void setZeusPercent(String paramString)
{
    String[] arrayOfString;
    if ((paramString != null) && (!paramString.equalsIgnoreCase("")) && (!paramString.equalsIgnoreCase("#")))
    {
		this.mZeusLstPercents = new ArrayList();
		arrayOfString = paramString.split(";;");
		if (arrayOfString != null)
			break label45;
    }
    while (true)
    {
		return;
		label45: for (int i = 0; i < arrayOfString.length; i++)
			this.mZeusLstPercents.add(Integer.valueOf(arrayOfString[i]));
    }
}

public void setZeusUrl(String paramString)
{
    String[] arrayOfString;
    if ((paramString != null) && (!paramString.equalsIgnoreCase("")) && (!paramString.equalsIgnoreCase("#")))
    {
		this.mZeusLstUrls = new ArrayList();
		arrayOfString = paramString.split(";;");
		if (arrayOfString != null)
			break label45;	
    }
    while (true)
    {
		return;
		label45: for (int i = 0; i < arrayOfString.length; i++)
			this.mZeusLstUrls.add(arrayOfString[i]);
    }
}

接着我们再来看下哪里在调用setZeusUrl和setZeusPercent进行偷偷的打开链接的行为。在fm.qingting.qtradio的QTRadioActivity类下面,找到了如下代码:

String str33 = MobclickAgent.getConfigParams(this, "doubleClickUrls");
if (str33 != null)
{
    DoubleClick.getInstance().setZeusUrl(str33);
    String str73 = MobclickAgent.getConfigParams(this, "doubleClickPercent");
    if (str73 != null)
    DoubleClick.getInstance().setZeusPercent(str73);
}

看来中招的是来自Google Adsense的doubleClick。除了Google的doubleclick还有谁中招?通过寻找startZeus(),我们找到了RootNode的类,类中出现了一个叫定时器的方法:

public void onClockTime(int paramInt)
{
    checkProgramNode(paramInt);
    checkLinkInfo();
    if (paramInt % 5 == 0)
    {
		updateDB();
		ThirdTracker.getInstance().trackJD();
    }
    if (paramInt % 2 == 0)
    {
		DoubleClick.getInstance().startZeus();
		DoubleClick.getInstance().startSuperZeus();
    }
    if (paramInt % 10 == 0)
    {
		Zeus.getInstance().startZeus();
		ThirdTracker.getInstance().startAM();
		ThirdTracker.getInstance().start();
		reloadAD();
    }
    long l = ThirdTracker.getInstance().getJDAdvTime();
    if ((l > 0L) && (paramInt >= l))
		ThirdTracker.getInstance().changeJD();
    if (paramInt % 20 == 0)
    {
		InfoManager.getInstance().runSellApps();
		if (InfoManager.getInstance().enableTBMagic())
		TaobaoAgent.getInstance().playAD(true);
    }
    if (paramInt % 60 == 0)
    {
		RecommendStatisticsUtil.INSTANCE.sendLog();
		if (this.mRecommendPlayingInfo.checkRecommendPlayingList(paramInt))
			InfoManager.getInstance().root().setInfoUpdate(2);
		IMAgent.getInstance().ping();
    }
}

定时器中还调用了ThirdTracker类,此次中招的还有admaster、京东及秒针。

public void setADPercent(String paramString)
{
    String[] arrayOfString;
    if ((paramString != null) && (!paramString.equalsIgnoreCase("")) && (!paramString.equalsIgnoreCase("#")))
    {
		this.mLstADPercents = new ArrayList();
		arrayOfString = paramString.split(";;");
		if (arrayOfString != null)
			break label46;
    }
    while (true)
    {
		return;
		label46: for (int i = 0; i < arrayOfString.length; i++)
			this.mLstADPercents.add(Integer.valueOf(arrayOfString[i]));
    }
}

public void setADUrl(String paramString)
{
    String[] arrayOfString;
    if ((paramString != null) && (!paramString.equalsIgnoreCase("")) && (!paramString.equalsIgnoreCase("#")))
    {
		this.mLstADUrls = new ArrayList();
		arrayOfString = paramString.split(";;");
		if (arrayOfString != null)
			break label46;
    }
    while (true)
    {
		return;
		label46: for (int i = 0; i < arrayOfString.length; i++)
			this.mLstADUrls.add(arrayOfString[i]);
    }
}

public void setJDAdv(List<CommodityInfo> paramList, boolean paramBoolean)
{
    this.changeRecvJdTime = (1L + RangeRandom.Random(this.INTERVAL) + System.currentTimeMillis() / 1000L);
    this.trueIMEI = paramBoolean;
    this.mLstJDAdv = paramList;
}

public void setJDSeed(int paramInt)
{
    this.mJDSeed = paramInt;
}

public void setMZPercent(String paramString)
{
    String[] arrayOfString;
    if ((paramString != null) && (!paramString.equalsIgnoreCase("")) && (!paramString.equalsIgnoreCase("#")))
    {
		this.mLstMZPercents = new ArrayList();
		arrayOfString = paramString.split(";;");
		if (arrayOfString != null)
			break label46;
    }
    while (true)
    {
		return;
		label46: for (int i = 0; i < arrayOfString.length; i++)
			this.mLstMZPercents.add(Integer.valueOf(arrayOfString[i]));
    }
}

public void setMZUrl(String paramString)
{
    String[] arrayOfString;
    if ((paramString != null) && (!paramString.equalsIgnoreCase("")) && (!paramString.equalsIgnoreCase("#")))
    {
		this.mLstMZUrls = new ArrayList();
		arrayOfString = paramString.split(";;");
		if (arrayOfString != null)
			break label46;
    }
    while (true)
    {
		return;
		label46: for (int i = 0; i < arrayOfString.length; i++)
			this.mLstMZUrls.add(arrayOfString[i]);
    }
}

回到刚才提到的定时方法onClockTime(),可以查询到这个定时器的调用者为:ClockManager中的dispatchClockEvent()

private void dispatchClockEvent(int paramInt)
{
    removeUnavailableListener();
    Iterator localIterator = new HashSet(this.listeners).iterator();
    while (localIterator.hasNext())
    {
		IClockListener localIClockListener = (IClockListener)((WeakReference)localIterator.next()).get();
		if (localIClockListener != null)
			localIClockListener.onClockTime(paramInt);
    }
}

那么dispatchClockEvent()方法又是谁在调用呢?

private Runnable clockRunnable = new Runnable()
{
    public void run()
    {
		long l1 = SystemClock.uptimeMillis();
		long l2 = l1 + (1000L - l1 % 1000L);
		int i = (int)(System.currentTimeMillis() / 1000L);
		ClockManager.this.dispatchClockEvent(i);
		if ((ClockManager.this.timerAvailable) && (i >= ClockManager.this.timerTarget))
		{
			ClockManager.this.dispatchTimeEvent(ClockManager.this.timer);
			ClockManager.this.timer.available = false;
			ClockManager.this.refreshTimerAvailable();
		}
		ClockManager.this.handler.postAtTime(ClockManager.this.clockRunnable, l2);
    }
};

可以看到上面的程序是个死循环程序,所以一直在运行着。

到此主要的作弊代码分析完了,感兴趣想深入研究的可以自己反编译。对于如此有才华的公司及程序员,我只能是膜拜膜拜再膜拜。另外提醒一句:善有善报,恶有恶报,不是不报,时辰未到!

相关 [app 作弊 代码] 推荐:

某APP作弊代码分析

- - 标点符
先前网上的一篇文章直指某APP数据作弊骗取投资人及广告商. 虽然文章中间也包含了部分的代码,但是为了进一步确认. 我下载该App的历史版本(曝光后最新版本作弊代码已被隐去)并进行反编译. 下载的该APP版本为:v5.0.1,反编译工具采用的是:onekey decompile apk. 后台偷偷启动进程,让APP在后台永活(后台启动了5个服务,相互保护).

面孔网:开放源代码,快速搭建自己的移动APP

- 奥斯汀Outman - Tech2IPO
有幸接触到面孔网三个核心创始人,一起聊到这个项目,起初在我刚刚接触到面孔网,在其首页只有一款基于新浪微博的网页端应用,在沟通之后才发现这只是这个项目的冰山一角. 面孔网的创始人任一是中国最早的Linux企业冲浪平台创始人,是国内第一个做Linux操作系统的人. 如今移动互联网上已经产生出新的入口和游戏规则,任一团队决定遵循开放的理念,在移动互联网领域做一些新的东西.

App 和 iCloud

- 笑炊 - 爱范儿 · Beats of Bits
iCloud 的技术细节还在 NDA 的保护下. 但是大家的好奇心不能等到 NDA 失效再满足. 本文基于对 iCloud 的猜测写成,靠谱与否,等待时间检验. 打开浏览器,嗯,今天用 Safari , Chrome , IE 或者 Firefox. 输入 Twiter.com ,啊,不对,是 Twitter.com.

App Internet 革命

- Cary - Mr. Jamie 看網路與創投
Apple 公布最新一季的財報,3 個月賣出了破紀錄的 3,500 萬台 iDevices (iPhone, iPad & iPods). Google 公布最新數字,全球有 1.9 億支 Android 已經被啟用. 大家很興奮「智慧型手機」、「行動裝置」革命終於來到,我卻隱隱感覺到另一件更重大的事情正在發生,我們所熟知的「網路」,即將經歷另一次大幅度的轉變.

浅析App Engine

- - 搜索研发部官方博客
在国内外,云计算正在大步的走向商业化的道路,也得到了越来越多公司的重视. 其中平台即服务(Platform-as-a-Service  PaaS)已经称为业界探讨云计算的热点方式之一,采用PaaS模式来构建应用运行平台App Engine是一种重要的实现方式. 本文主要是对App Engine的背景、特点、需求等进行分析整理,并据此对业界主要的App Engine进行了调研分析.

Mobile App 将死?!

- - Tech2IPO
日前,Mozilla 产品副总监 Jay Sullivan 称移动应用不久即将成为历史,未来将是移动 Web 应用的天下. 光盘好歹还能当杯垫,可怜 Mobile App,难道就这样一下跌落进历史的垃圾堆. Mozilla 的产品副总监杰 • 沙利文 (Jay Sullivan, 上图) 日前表示,移动终端应用(Mobile App)没有未来,真正有前途的是移动 Web 应用(Mobile Web App).

APP已死?

- - 商业不靠谱
APP目前面临的几大窘境将促使搜索引擎由Search向Service、Getting 转变以适应用户在APP时代养成的简洁、高效等习惯. 《未来移动终端应用 C/S Vs B/S 架构》 许永硕——物联网智库. 参照PC软件的发展历程,B/S架构或许是破解APP难题的出路,目前,微信开放平台、手机QQ等在尝试扮演Browser(http://open.weixin.qq.com).

欺诈 app 追杀 — 给 App Store 的信

- Webto - Wangling
感谢 @apple4us 的建议. 我深知如果等着别人相助,此事大概会不了了之,届时只徒留一篇愤概文章. 所谓“追杀”,敌未死,我未停,正如给“动车追尾”事件的受害人追讨公道,公道未到,追讨不止. 于是,我刚给 App Store 发了信,如下:. 每人干掉一个坏蛋…,坏蛋没那么多;每一百个人、每一千个人、甚至每一万个人干掉一个坏蛋,世界都会美好许多.

Web App和Native App 谁将是未来

- - 互联网旁观者
未来是Web App的天下,还是Native App的天下. 作为设计师,我们是应该努力把客户端的体验提升到最优,还是在网页应用层面上做更多的设计. 那么,我们首先应该立体的认识一下Web App和Native App. Web 无需安装,对设备碎片化的适应能力优于App,它只需要通过XHTML、CSS和JavaScript就可以在任意移动浏览器中执行.

Blogger也有App了!

- 幻幽 or A書 - Jas9 Taipei.
感覺上,彷彿自從Google+推出之後,所有Google的既有產品服務都積極活動了起來. 繼上週Blogger的新後台從Draft轉到正式版更新上線之後,Google終於也推出期待多時的官方版本Blogger App.