最近信息中心开展了一系列睡觉前欢乐时光活动,以游戏成绩排名来获得32M宽带组激活资格。
从活动开始到现在已经有2个游戏,分别为打兔兔和撞死鸟。
其中只要每次超过第一名,则可以激活32M宽带组3天时长,如果没有超过第一名也没有关系,达到一定的条件则有机会随机获得32M资格。
活动很有意义的调动了学院不同学生的兴趣爱好,喜欢玩游戏的可以在睡觉之前娱乐一下,喜欢搞按键精灵模拟的同学可以趁机写出外挂,喜欢网络协议分析的同学可以从协议层面出发来破解游戏数据,甚至还有喜欢搞破解的可以直接修改游戏数据等等。。。
当然,我也属于这其中一类。
本文章适合有一点儿Javascript基础、Html基础和网络基础的同学。
从打兔兔游戏到撞死鸟游戏,基本上每一次游戏的改版变更都体验和参与了,而几乎没有一次完完全全玩完过游戏,因为多的时间都花在分析破解上面了。鉴于最近准备不继续搞下去了,所以就此写此文,希望能够给想要继续破解的同学们一些思路,贡献自己的一些拙见。
首先从目前游戏整体上来说,
目前游戏是通过AAA客户端以消息形式,在每天晚上的一定的时间由老师手动批量发给预约用户。客户端收到消息包后,提取消息信息,根据消息信息指定的内容来显示消息窗体。
因为游戏本身为Html5所写的,所以自然消息窗体是通过WebBrowser控件来载入html源码显示的,因而要想破解游戏首先要获取游戏源码。
而客户端为了防止蛋疼的人士直接获取源码,最初是把Html源码直接以消息形式发给每一个客户,而每一个人得到的源码不同(因为有PostScore函数参量,每个人学号不同),因而是无法通过嗅探地址方式得到源码,因而抓包软件抓出来的消息也只是客户端的消息封装,无法直接读取出源码。所以这条思路是无法走通的。因而,比较直接的方法是要通过Hook结合注入句柄(控件)的方法来直接读取里面的源码。
而到了撞死鸟游戏,游戏消息并非是通过发给客户端Html 源码,而是直接把游戏做在了网页上面。系统发给客户端的是网页连接的URL,客户端收到URL之后,会让WebBrowser直接调用Url。当然这个Url也是做了一些限制处理的,后面会慢慢讲到。
这其中会有很多步骤,自己手动实现难度太大,恰好在网上找到一个可以实现这个功能的工具:MySpy(下载地址:MySpy)
-----------------------------------------------------------------------------------------------------------------------------------------
首先从打兔兔开始,因为电脑屏幕分辨率问题,DPI缩放导致截屏有问题,所以整篇文章没有图片请谅解。
第一步:当游戏弹出的时候,打开MySpy(以管理员身份运行),切换到网页选项卡,将工具左侧的ie图标拖动到游戏窗口,此时可以看到Myspy上面显示了窗体信息,包括部分源码,点击“获取网页源码”,则会自动打开TXT文件,并将源码写在里面,此时源码得到。
第二步:
<HTML><HEAD><TITLE>打兔子</TITLE> <META content=text/html;charset=utf-8 http-equiv=content-type> <STYLE type=text/css media=all> @import "http://aaa.nsu.edu.cn/happygametime/1/css/main.css"; </STYLE> <SCRIPT src="http://aaa.nsu.edu.cn/happygametime/1/scripts/string_library.js?3"></SCRIPT> <SCRIPT src="http://aaa.nsu.edu.cn/happygametime/1/scripts/clouds.js?3"></SCRIPT> <SCRIPT src="http://aaa.nsu.edu.cn/happygametime/1/scripts/bunnies.js?3"></SCRIPT> <SCRIPT src="http://aaa.nsu.edu.cn/happygametime/1/scripts/preload.js?3"></SCRIPT> <SCRIPT> alert("由于之前第一次的窗口发错了日期,许多同学又不小心把第二次正确的给关掉了。应同学们要求现在重新补发一次!") function PostScore(starttime,endtime,level,score) { var PS='1131022XXXX|071EbPjwiyiysXXXXXX+w==|169.254.18.195,10.122.8.12,169.254.7.93,192.168.10.1,192.168.56.1|1|'+starttime+'|'+endtime+'|'+level+'|'+score+'|27'; PS=PS+'|'+hex_md5(PS); var js = document.createElement('script'); js.src = encodeURI('http://aaa.nsu.edu.cn/happygametime/PostScore.aspx?v=' + PS); document.getElementsByTagName('head')[0].appendChild(js) } setTimeout('cloudsInit();preloadImages();', 500) </SCRIPT> <SCRIPT src="http://aaa.nsu.edu.cn/happygametime/PostScore.aspx?v=11310220202%7C071EbPjwiyiysfcfbwdK+w==%7C169.254.18.195,10.102.8.12,169.254.7.93,192.168.10.1,192.168.56.1%7C1%7C1395928583275%7C1395928620358%7C3%7C16%7C27%7C0c64cd58a72771f5e2c80f324a84302e"></SCRIPT> <SCRIPT src="http://aaa.nsu.edu.cn/happygametime/PostScore.aspx?v=11310220202%7C071EbPjwiyiysfcfbwdK+w==%7C169.254.18.195,10.102.8.12,169.254.7.93,192.168.10.1,192.168.56.1%7C1%7C1395928782352%7C1395928810769%7C3%7C7%7C27%7C6124bec2861eb18892395de71557db04"></SCRIPT> <SCRIPT src="http://aaa.nsu.edu.cn/happygametime/PostScore.aspx?v=11310220202%7C071EbPjwiyiysfcfbwdK+w==%7C169.254.18.195,10.102.8.12,169.254.7.93,192.168.10.1,192.168.56.1%7C1%7C1395928857189%7C1395928883557%7C2%7C6%7C27%7Cef21a196153d000bcbf28e1cade1703e"></SCRIPT> </HEAD> <BODY id=MySpy_Element> <DIV id=splash> <DIV id=loadingMessage> <P><STRONG>加载完成,可以开始了!每次只能玩10局,珍惜每次机会!</STRONG></P> <DIV> <DIV style="WIDTH: 100%"></DIV></DIV><INPUT height=37 src="http://aaa.nsu.edu.cn/happygametime/1/images/button_start.gif" type=image width=213></DIV></DIV> <DIV id=stage class="ready gameOver"> <DIV id=level currTime="12" timer="21105"> <H2>Level: </H2> <P>2</P> <DIV> <DIV style="WIDTH: 100%"></DIV></DIV></DIV> <DIV id=lives class="lost1 lost2 lost3" livesLost="3"> <H2>Lives: </H2></DIV> <DIV id=score> <H2>Score: </H2> <P>6</P></DIV> <DIV id=hill1></DIV> <DIV id=hill2></DIV> <DIV id=hill3></DIV> <DIV id=cloud1 style="LEFT: 271px"></DIV> <DIV id=cloud2 style="LEFT: 29px"></DIV> <DIV id=bunny1 class="bunny bunny2" style="MARGIN-BOTTOM: 0px; MARGIN-LEFT: 0px; MARGIN-RIGHT: 0px" timer="20915" target="false" direction="up" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny2 class="bunny bunny3" style="MARGIN-BOTTOM: 0px; MARGIN-LEFT: 0px; MARGIN-RIGHT: 0px" timer="21124" target="false" direction="up" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny3 class="bunny bunny1" style="MARGIN-BOTTOM: 133px; MARGIN-LEFT: 0px; DISPLAY: block; MARGIN-RIGHT: 0px" timer="21193" target="true" direction="down" blinkCounter="0" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny4 class="bunny bunny3" style="MARGIN-BOTTOM: 0px; MARGIN-LEFT: 0px; DISPLAY: block; MARGIN-RIGHT: 0px" timer="20066" target="false" direction="up" blinkCounter="0" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny5 class="bunny bunny1" style="MARGIN-BOTTOM: 0px; MARGIN-LEFT: 0px; DISPLAY: block; MARGIN-RIGHT: 0px" timer="20575" target="false" direction="up" blinkCounter="0" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny6 class="bunny bunny2" style="MARGIN-BOTTOM: 0px; MARGIN-LEFT: 0px; DISPLAY: block; MARGIN-RIGHT: 0px" timer="21081" target="false" direction="up" blinkCounter="0" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny7 class="bunny bunny3" style="MARGIN-BOTTOM: 0px; MARGIN-LEFT: 0px; DISPLAY: block; MARGIN-RIGHT: 0px" timer="21190" target="false" direction="up" blinkCounter="0" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny8 class="bunny bunny1" style="MARGIN-BOTTOM: 0px; MARGIN-LEFT: 0px; MARGIN-RIGHT: 0px" timer="20808" target="false" direction="up" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny9 class="bunny bunny2" style="MARGIN-BOTTOM: 0px; MARGIN-LEFT: 0px; MARGIN-RIGHT: 0px" timer="21012" target="false" direction="up" dropped="false"> <DIV></DIV></DIV> <DIV id=bunny10 class="bunny bunny4"> <DIV></DIV></DIV> <DIV id=bunny11 class="bunny bunny5"> <DIV></DIV></DIV> <DIV id=bomb1 style="MARGIN-LEFT: 0px"> <DIV></DIV></DIV> <DIV id=bomb2 style="MARGIN-RIGHT: 0px"> <DIV></DIV></DIV> <DIV id=cover></DIV> <DIV id=message>(3/10) 你惨无人道谋杀的兔子数量: <P>6</P><INPUT height=37 src="http://aaa.nsu.edu.cn/happygametime/1/images/button_reload.gif" type=image width=193><INPUT height=37 src="http://aaa.nsu.edu.cn/happygametime/1/images/button_stop.gif" type=image width=193><SPAN><BR><BR><SPAN style="COLOR: green">很遗憾,你的杀兔数量与今日最高纪录相差 <SPAN style="COLOR: yellow">527</SPAN> 只。<BR>打破今日杀兔纪录,快乐时光免费送32M体验资格哦~<BR>嘘~~悄悄告诉你:只要努力认真打兔兔,32M资格随机都有送哦~</SPAN></SPAN></DIV> <DIV id=levelMessage style="DISPLAY: none; opacity: 0.99"></DIV></DIV> <DIV id=closing></DIV><BR> <OBJECT id=wmp width=0 height=0 type=application/x-mplayer2></OBJECT><SPAN onclick=wmp.Stop(); id=m1 style="CURSOR: pointer; COLOR: blue">关闭音乐</SPAN> <SPAN onclick=wmp.Play() id=m2 style="CURSOR: pointer; COLOR: green">开启音乐</SPAN> <SPAN style="COLOR: red">加油!每局游戏达到5关和30只兔子均有很大机会获得32M体验资格哦!</SPAN> <A href="http://aaa.nsu.edu.cn/HappyGameTime/Top100.aspx?GID=1&UID=11310220202" target=_blank>点击此处查看今日游戏得分排行榜</A></BODY></HTML>
分析源码可以很清楚的看到,游戏数据最后通过PostScore函数来发送游戏成绩到服务器,因而该函数就是关键。可以看到函数是通过构造PS变量来得到成绩,变量中首先是学号、密码、IP地址,然后就是游戏成绩的一些基本参数。然后PS变量最后会附加上PS本身的MD5值在结尾,可以视为是服务器对提交数据的校验。
因而,清楚的知道了提交数据的结构,就可以构造自己的数据提交给接口。
当然,没那么简单,服务器在提交来源做了限制,没有深入的研究下去,就放弃了直接提交数据的想法。
既然不能直接提交数据,那么整个游戏是离线模式运行,最后才提交数据给服务器,则游戏运行本身中的数据则有方法修改。
继续深入看源码可以知道:http://aaa.nsu.edu.cn/happygametime/1/scripts/bunnies.js?3 为游戏主要控制的JS,直接输入URL到浏览器,得到该JS文件可以看到对PostScore调用:
PostScore(starttime,endtime,level,scoreP.firstChild.nodeValue);
继续看上下文,找到scoreP变量:
var scoreP = score.getElementsByTagName("p")[0];
可以看到ScoreP变量是直接通过getElementsByTagName来获取当前网页
标签的值。
综上,可以看到,只需要在游戏进行过程中修改ScoreP的值,即可提交伪造成绩给服务器。
继续使用Myspy,依然在网页选项卡中通过拖动IE图标到游戏窗口,来Hook到控件,然后点运行脚本,在弹出的框中输入:
var scoreP = score.getElementsByTagName("p")[0];
scoreP=999;
先运行游戏,正常开始打兔子,然后点击Myspy的脚本窗口,然后点运行,如果脚本没有错误,此时可以看到打兔兔窗口中的成绩已经变为了999,随便打几个兔子死掉,则可以提交999成绩到服务器,冲击第一名,手工。
当然,不会一直这么简单,信息中心后来对游戏升级,加入了防破解机制,但无非也是通过一个全局变量来多次检测,只要同时改变这几个变量的值就行了,于是我的脚本经过几次变化:
var scoreP = score.getElementsByTagName("p")[0];
scoreP=999;
click=999;
ccc=999;
starttime=1397401800112;
这基本就是打兔兔时代我所伪造提交的脚本。当然高手们还可以继续挖掘,比如修改等级,修改兔子出现时间等等。
--------------------------------------------------------------------------------------------------------------
游戏进入到撞死鸟时代,
撞死鸟又名flyppy bird,信息中心对整个游戏进行了改良,最初游戏上线当天因为回家,所以没有体验到,当体验到游戏的时候本以为可以像之前打兔兔一样分析源码来修改成绩,但想简单了。
游戏由以前的推送html源码变成了推送的是URL链接,实际上在窗体中显示的网页为地址:http://aaa.nsu.edu.cn/HappyGameTime/2/default.aspx 而这个地址是无法直接通过IE等浏览器来访问,如果访问都是提示 不能直接访问。
当然依然可以用MySpy来获取源码,没有对源码进行保存,今晚如果还有打兔兔游戏的各位可以试试,可以看到关键的JS脚本为地址:?js/main.js.nsu 同样,这个地址和之前的游戏主页地址一样都是无法直接进行访问。通过抓包可以看到,AAA客户端的消息窗体在访问的时候是带有Cookie的,并且Cookie为:T:11310XXXXX;P:XXXXXXX;K:XXXXXXX这种形式,通过Http协议分析器尝试伪造发现不管用,因此判定Cookie有刷机机制。
直接无法获取源码的话,想到客户端要想显示这些网页内容必然会请求这些数据到本地,因而,可以直接通过查找缓存文件夹中的main.js.nsu得到该文件,同样也可以通过抓包软件来获取JS文件内容。
获取到JS文件内容后发现为加密的,随便找一个在线JS解密网站,解密得到如下撞死鸟游戏的核心源码:
function showSplash() { currentstate = states.SplashScreen, setBigScore(!0), velocity = 0, position = 180, rotation = 0, score = 0, $("#player").css({ y: 0, x: 0 }), updatePlayer($("#player")), soundSwoosh.stop(), soundSwoosh.play(), $(".pipe").remove(), pipes = new Array, $(".animated").css("animation-play-state", "running"), $(".animated").css("-webkit-animation-play-state", "running"), $("#splash").transition({ opacity: 1 }, 2e3, "ease") } function startGame() { var a, b; pipeheight = 150, iszb = 0, playcount += 1, a = $("#footer").html(), $("#footer").html("<span style='color:#880'>" + (new Date).toLocaleString() + " (" + playcount + "/10) ...</span><br/>" + a), starttime = (new Date).getTime(), lastscore = 0, currentstate = states.GameScreen, $("#splash").stop(), $("#splash").transition({ opacity: 0 }, 500, "ease"), setBigScore(), debugmode && $(".boundingbox").show(), b = 1e3 / 60, loopGameloop = setInterval(gameloop, b), loopPipeloop = setInterval(updatePipes, 1400), playerJump() } function updatePlayer(a) { rotation = Math.min(90 * (velocity / 10), 90), $(a).css({ rotate: rotation, top: position }) } function gameloop() { var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r = $("#player"); return velocity += gravity, position += velocity, updatePlayer(r), a = document.getElementById("player").getBoundingClientRect(), b = 34, c = 24, d = b - 8 * Math.sin(Math.abs(rotation) / 90), e = (c + a.height) / 2, f = (a.width - d) / 2 + a.left, g = (a.height - e) / 2 + a.top, h = f + d, i = g + e, debugmode && (j = $("#playerbox"), j.css("left", f), j.css("top", g), j.css("height", e), j.css("width", d)), a.bottom >= $("#land").offset().top ? (playerDead(), void 0) : (k = $("#ceiling"), g <= k.offset().top + k.height() && (position = 0), null != pipes[0] ? (l = pipes[0], m = l.children(".pipe_upper"), n = m.offset().top + m.height(), o = m.offset().left - 2, p = o + pipewidth, q = n + pipeheight, debugmode && (j = $("#pipebox"), j.css("left", o), j.css("top", n), j.css("height", pipeheight), j.css("width", pipewidth)), h > o && !(g > n && q > i) ? (playerDead(), void 0) : (f > p && (pipes.splice(0, 1), playerScore()), void 0)) : void 0) } function screenClick() { currentstate == states.GameScreen ? playerJump() : currentstate == states.SplashScreen && startGame() } function playerJump() { velocity = jump, soundJump.stop(), soundJump.play() } function setBigScore(a) { var b, c, d = $("#bigscore"); if (d.empty(), !a) for (b = score.toString().split(""), c = 0; c < b.length; c++) d.append("<img src='assets/font_big_" + b[c] + ".png' alt='" + b[c] + "'>") } function setSmallScore(a) { var b, c, d = $("#currentscore"); for (d.empty(), b = a.toString().split(""), c = 0; c < b.length; c++) d.append("<img src='assets/font_small_" + b[c] + ".png' alt='" + b[c] + "'>") } function setHighScore(a) { var b, c, d = $("#highscore"); for (d.empty(), b = a.toString().split(""), c = 0; c < b.length; c++) d.append("<img src='assets/font_small_" + b[c] + ".png' alt='" + b[c] + "'>") } function setMedal() { var a = $("#medal"); return a.empty(), 10 > score ? !1 : (score >= 10 && (medal = "bronze"), score >= 20 && (medal = "silver"), score >= 30 && (medal = "gold"), score >= 40 && (medal = "platinum"), a.append('<img src="assets/medal_' + medal + '.png" alt="' + medal + '">'), !0) } function playerDead() { var a, b, c; $(".animated").css("animation-play-state", "paused"), $(".animated").css("-webkit-animation-play-state", "paused"), a = $("#player").position().top + $("#player").width(), b = $("#flyarea").height(), c = Math.max(0, b - a), $("#player").transition({ y: c + "px", rotate: 90 }, 1e3, "easeInOutCubic"), currentstate = states.ScoreScreen, clearInterval(loopGameloop), clearInterval(loopPipeloop), loopGameloop = null, loopPipeloop = null, isIncompatible.any() ? postScore() : (postScore(), soundHit.play().bindOnce("ended", function() { soundDie.play().bindOnce("ended", function() {}) })) } function postScore() { var a; 0 == iszb && 1 >= score - lastscore ? (a = $("#footer").html(), $("#footer").html("<span style='color:#080'>" + (new Date).toLocaleString() + " (" + playcount + "/10) ...</span><br/>" + a), $.post("../PostScore.aspx", { G: 2, C: playcount, T: starttime, S: score, L: 0, K: hex_md5("2 " + playcount + " " + starttime + " " + score + " 0") }, function(a) { var b, c = $("#footer").html(); $("#footer").html("<span style='color:#00B'>" + (new Date).toLocaleString() + " (" + playcount + "/10) ...</span><br/>" + c), 0 == a.indexOf("OK|") ? (b = a.split("|"), c = $("#footer").html(), $("#footer").html("<span style='color:#f00'>" + b[1] + "</span><br/>" + c), showScore(b[3], b[2])) : (c = $("#footer").html(), $("#footer").html("<span style='color:#000'>! :" + a + "</span><br/>" + c), showScore(0, 0)) })) : (a = $("#footer").html(), $("#footer").html("<span style='color:red'>" + (new Date).toLocaleString() + " (" + playcount + "/10) ,!...</font><br/>" + a), showScore(0, 0)) } function showScore(a, b) { $("#scoreboard").css("display", "block"), setSmallScore(a), setHighScore(b); var c = setMedal(); soundSwoosh.stop(), soundSwoosh.play(), $("#scoreboard").css({ y: "40px", opacity: 0 }), $("#replay").css({ y: "40px", opacity: 0 }), $("#scoreboard").transition({ y: "0px", opacity: 1 }, 600, "ease", function() { if (soundSwoosh.stop(), soundSwoosh.play(), $("#replay").transition({ y: "0px", opacity: 1 }, 600, "ease"), playcount >= 10) { var a = $("#footer").html(); $("#footer").html("<span style='color:#0BB'></span><br/>" + a) } c && ($("#medal").css({ scale: 2, opacity: 0 }), $("#medal").transition({ opacity: 1, scale: 1 }, 1200, "ease")) }), replayclickable = !0 } function playerScore() { score - lastscore > 1 && (iszb = 1), lastscore = score, score += 1, pipeheight > 130 && (pipeheight -= 1), 130 >= pipeheight && pipeheight > 110 && 0 == score % 2 && (pipeheight -= 1), 110 >= pipeheight && pipeheight > 90 && 0 == score % 3 && (pipeheight -= 1), 90 >= pipeheight && pipeheight > 70 && 0 == score % 4 && (pipeheight -= 1), soundScore.stop(), soundScore.play(), setBigScore() } function updatePipes() { var a, b, c, d, e; $(".pipe").filter(function() { return $(this).position().left <= -100 }).remove(), a = 80, b = 420 - pipeheight - 2 * a, c = Math.floor(Math.random() * b + a), d = 420 - pipeheight - c, e = $('<div class="pipe animated"><div class="pipe_upper" style="height: ' + c + 'px;"></div><div class="pipe_lower" style="height: ' + d + 'px;"></div></div>'), $("#flyarea").append(e), pipes.push(e) } var currentstate, loopGameloop, loopPipeloop, playcount, starttime, lastscore, iszb, isIncompatible, debugmode = !1, states = Object.freeze({ SplashScreen: 0, GameScreen: 1, ScoreScreen: 2 }), gravity = .25, velocity = 0, position = 180, rotation = 0, jump = -4.6, score = 0, highscore = 0, pipeheight = 150, pipewidth = 52, pipes = new Array, replayclickable = !1, volume = 100, soundJump = new buzz.sound("assets/sounds/sfx_wing.mp3"), soundScore = new buzz.sound("assets/sounds/sfx_point.mp3"), soundHit = new buzz.sound("assets/sounds/sfx_hit.mp3"), soundDie = new buzz.sound("assets/sounds/sfx_die.mp3"), soundSwoosh = new buzz.sound("assets/sounds/sfx_swooshing.mp3"); buzz.all().setVolume(volume), $(document).ready(function() { "?debug" == window.location.search && (debugmode = !0), "?easy" == window.location.search && (pipeheight = 200), $("#welcomediv").click(function() { $("#gamediv").show(), $("#welcomediv").hide(), showSplash() }) }), playcount = 0, starttime = 0, lastscore = 0, iszb = 0, $(document).keydown(function(a) { 32 == a.keyCode && (currentstate == states.ScoreScreen ? $("#replay").click() : screenClick()) }), "ontouchstart" in window ? $(document).on("touchstart", screenClick) : $(document).on("mousedown", screenClick), $("#replay").click(function() { return replayclickable ? (replayclickable = !1, soundSwoosh.stop(), soundSwoosh.play(), playcount >= 10 ? ($("#goodbyediv").show(), $("#gamediv").hide(), void 0) : ($("#scoreboard").transition({ y: "-40px", opacity: 0 }, 1e3, "ease", function() { $("#scoreboard").css("display", "none"), showSplash() }), void 0)) : void 0 }), isIncompatible = { Android: function() { return navigator.userAgent.match(/Android/i) }, BlackBerry: function() { return navigator.userAgent.match(/BlackBerry/i) }, iOS: function() { return navigator.userAgent.match(/iPhone|iPad|iPod/i) }, Opera: function() { return navigator.userAgent.match(/Opera Mini/i) }, Safari: function() { return navigator.userAgent.match(/OS X.*Safari/) && !navigator.userAgent.match(/Chrome/) }, Windows: function() { return navigator.userAgent.match(/IEMobile/i) }, any: function() { return isIncompatible.Android() || isIncompatible.BlackBerry() || isIncompatible.iOS() || isIncompatible.Opera() || isIncompatible.Safari() || isIncompatible.Windows() } };
可以看到整个游戏并未通过JS来控制游戏,而是通过CSS3动画来控制游戏,不过这依然不影响成绩分析。继续分析源码,可以看到PostScore函数,看函数流程可以看到:
首先函数判定iszb变量是否为1,同时判定score和lastscore变量值相差是否超过1,如果超过则判定为作弊。然后函数将游戏数据打包发给服务器,完成数据提交,然后程序将服务器返回的信息显示在窗口上。
从这个流程可以看出,如果要修改成绩需要同时保证iszb变量值为0,score和lastscore变量值相差不超过1,因此在MySpy中提交给窗口的脚本为:
score=999;
lastscore=999;
iszb=0;
starttime=1397401800112;
starttime为游戏开始时间,为了便于数据不被轻易查出来,所以 此处的时间需要进行换算,在网页 时间转换 中输入年月日时间,即可生成这个时间,但生成的时间没有毫秒的,所以要手动补足最后三位。即例如生成的时间为1397401800,则正确数据为1397401800+112(最后三位可以随意构造)。
这样就可以获得999的成绩。
至此为2014年4月17日晚前的分析修改方法。当然,整个游戏在后面会有更多变态的方法来防止破解,博主也没有更多的精力来去分析这些东西了,因而把这点儿思路和拙见给各位成都东软学院的同学。对于这个破解有兴趣的同学欢迎继续深入探索,相互交流。不过博主估计,以后无论游戏怎么改变,通常会在以下方面进行改进:对JS进行GZIP压缩,从而让抓包工具抓到的都是乱码-》可以通过提取本地缓存来解决;在JS中进行闭包机制检测来变量-》通过构造函数来进行欺骗;。。。。等等等等。
本文章也是在征求信息中心老师同意的情况下发出,因而没有对着干的意思。
PS.给看完整个文章的小伙伴一些福利:
1.无视10次游戏限制,一直玩游戏方法:在Myspy中运行脚本:location.reload();即可刷新网页。
2.....暂时想不起来了...
PS2.
1.关于Cookie机制的小提示:每秒钟一变,算法为通用算法中的一种。当然某个值也参与了计算的。(不要问我为什么知道,水表在外面)
2.话说打兔兔时代其实第二个变量为密码,而这个密码其实可以xxxxx。这个密码在注册表中也有,注册表地址我发过一次....(我不要社区送温暖)。。。
不过游戏本身是一个娱乐的平台,希望修改的成绩提交不要影响到其他人,博主之前都是等10点以后成绩大部分人都稳定了才开始慢慢提交数据给服务器来获得第一名。希望看到这篇文章的人以研究为目的,如果研究出成功的方法,请在大部分人成绩稳定之后再提交,尽量获取32M而不破坏公平性。
Comments 1 条评论
博主 MG
哇,原来东软以前还有这种有趣的东西。