AIS CTF Series – 1.2. – Timer

Spexeah Community

This is the second challenge in the AIS CTF Challenge Series, and still revolves around client-side protections.

Timer

This challenge gives us the following message “Wait until the timer completes to press the submit button.”, the timer seems to start at a rather large number and decrements every second:

That’s a lot of seconds!

We could wait for the next ~35.5 days (((3154856/60)/60)/24)=36.514 and then press the submit button, but where’s the fun in that? Besides, we’ll be back at work by then, there has to be an easier way.

Messing with the console doesn’t yield much of use, there’s a HackerChallenge object:

monospace;font-weight: bold;font-size: 14px"
​csrfToken: "3sYAIWEmSe1ad1eM0B7OYPvecD4AOmEOGtu5lyY2eTXihBhx8qlc4HF2ySov3KqV"
​displayBinary: function displayBinary(subpath, link_name, md5, sha256)​
endTimer: function endTimer(display)​
formatAsSourceCode: function formatAsSourceCode(text)​
getBinaryLink: function getBinaryLink(subpath, link_name)​
getChallenge: function getChallenge(challenge_id)
​getChallengeUrl: "/challenge/get-challenge/placeholder/"
​getChallenges: function getChallenges()
​getChallengesUrl: "/challenge/get-challenges/"
​mobileCheck: function mobileCheck()​
resumeSession: function resumeSession(account_id)
​resumeSessionUrl: "/challenge/resume-session/"
startTimer: function startTimer(seconds, display, tick_cb)​
stopTimer: function stopTimer(display, reason)​
submitAnswer: function submitAnswer(challenge_id, answer)
submitAnswerUrl: "/challenge/submit-answer/"
updatePointsCallback: function setScore(new_score)​

There’s some interesting objects in there; submitAnswer(challenge_id, answer) will be useful in future challenges, however, the functions that seem specific to this challenge (endTimer, startTimer, stopTimer) all expect a “display” object as a parameter, I’m not entirely sure what that is, I’m sure there’s a way to get it, but I’m also convinced there’s got to be an easier way.

Refreshing the page with the network tab & a filter set for .js files reveals some interesting files:

Inside the timer.js file we find the following:

function Timer(tag, action) {
 //-snip-
}


function Timer_load(tag) {
    //-snip-
    let promise = $.Deferred();

    localStorage.setItem("hackerchallenge.timer.resp", tag.challenge.resp);

    if (localStorage.getItem("hackerchallenge.timer") === null) {
        localStorage.setItem("hackerchallenge.timer", 3155760);
    }
    //-snip-
}

function Timer_submit(tag) {
 //-snip-
}


function Timer_load(tag) {
 //-snip-
}

function Timer_submit(tag) {
 //-smip-
}

From looking at the above Javascript, it looks to me that this script is the initiator for the timer, called by jquery, but I wasn’t satisfied this file would be easy to reflect from/modify. However, I did notice that the current remaining time seems to be written to the a localstorage item:

There’s a timer.resp there too! However, no matter how much I tried to modify this value, it would keep falling back to it’s real value, looks like a red herring to me.

Looking at hackerchallenge.js yields some much more promising logic, including the decrementing logic for the timer:

var HackerChallenge;


(function () {
    /* global $ */
    /* global riot */

    if (HackerChallenge === undefined) {
        HackerChallenge = {};
    }


    HackerChallenge.updatePointsCallback = function(new_points) {
        //-snip-
    }


    HackerChallenge.mobileCheck = function() {
        //-snip-
    };


    HackerChallenge.getBinaryLink = function(subpath, link_name) {
        //-snip-
    };


    HackerChallenge.displayBinary = function(subpath, link_name, md5, sha256) {
        //-snip-
    };


    HackerChallenge.submitAnswer = function(challenge_id, answer) {
        //-snip-
    };


    HackerChallenge.resumeSession = function(account_id) {
        //-snip-
    };


    HackerChallenge.getChallenges = function() {
        //-snip-
    };


    HackerChallenge.getChallenge = function(challenge_id) {
        //-snip-
    };


    HackerChallenge.stopTimer = function(display, reason="timer.stop") {
        //-snip-
    };

    HackerChallenge.endTimer = function(display) {
        //-snip-
    }

    HackerChallenge.startTimer = function(seconds, display, tick_cb=null) {
        //-snip-

        if (seconds < 0) {
            seconds = 0;
        }

        display.text(seconds);
        seconds = seconds - 1;

        var timerId = setInterval(function() {
            display.text(seconds);
            seconds = seconds - 1;

            if (tick_cb) {
                tick_cb(seconds);
            }

            if (seconds <= 0) {
                HackerChallenge.endTimer(display);
            }
        }, 1000);

        display.data("_timerId", timerId);

        return timerPromise;
    };

    //-snip-
    
    };
})();

It was a while into playing with this code from the console that R4Wizard reminded me that you can add breakpoints to code from the Firefox debugger… ofcourse! Sticking a breakpoint on the line that reads seconds = seconds - 1; by clicking the gutter next to that line and then refreshing the page causes our application to stop on that line.

From this breakpoint we can now access the “seconds” variable from our console as the console has access to the debuggers context.

Resuming the application, our timer jump down to 10 seconds, and once it hits zero, we can submit our answer and receive 50 more juicy points.

Hoorah! We didn’t have to wait a month!

Leave a Reply