Hexcellents CTF Wiki

Plaid CTF 2013: "Hypercomputer-2" 150 pts

For those who didn't play plaidCTF 2012: "supercomputer" was a reversing challenge that computed flags using really silly math (like adding in a loop instead of mulitplication). hypercomputer is easier... if you do it right :P
ssh to 54.224.174.166
HINT: You may notice that hypercomputer-2 is different... You should not be pwning or doing privilege escalation to solve it, though.

Part 2 was, as it says in the hint, different from part 1: after printing the 1st key, a noreturn procedure is called from the main function. Apart from some decoys it seemingly does not do anything useful, it's just an infinite loop so how do we recover the flag? Thinking outside the box you can ask yourself why wasn't this binary provided for download like all the rest? Why do you need to ssh to a server to grab it?
Returning to the main function we can clearly see that a file is opened, read and closed using argv[1].

So maybe there's a file somewhere up for grabs on the server. Doing a find didn't yield any useful results but what if there's still a hypercomputer process active? Having an infinite loop means it clearly won't exit on its own.

$ ps fax | grep -i hyper
  606 ?        Ss   181:39 ./hypercomputer /mnt/MyUSBStick/secret.txt

Great, but it's not that simple. The file was deleted and we have no access to the memory of that process. However the challenge says that there's no privilege escalation involved so there should be a way to read it. But how ? Let's turn to the disassembly:

The procedure repeatedly scans through the string read from the file and that's about it. Or is it? Notice the call to sleep (thanks to clockish for drawing my attention to the sleep call, when solving part 1 I deleted all sleep calls thinking there was no use in them anywhere in the app) it gets called with a variable parameter depending on the currently scanned character. 0x40 is 'A' - 1. So when it encounters an A it sleeps for 1*(0x186A0)+64 microseconds so ~100ms. For B ~200ms and so on.
Initially I mistakenly thought it was 1 second and tried using a shell script to check every second whether the process was Sleeping or Running using /proc/606/stat but the pattern wasn't repeating so ultimately I redid the math. Unfortunately this means we need a more accurate time source: ps won't work, top won't work and getrusage() is limited to the owner process (and its children). Fortunately, CONFIG_SCHEDSTATS is enabled in the kernel so we have /proc/606/sched:

hypercomputer (606, #threads: 1)
---------------------------------------------------------
se.exec_start                      :     221648972.347820
se.vruntime                        :      15813317.572043
se.sum_exec_runtime                :      15753121.517518
se.avg_overlap                     :             0.959737
se.avg_wakeup                      :             1.000000
se.avg_running                     :            64.850444
nr_switches                        :               157349
nr_voluntary_switches              :                57325
nr_involuntary_switches            :               100024
se.load.weight                     :                 1024
policy                             :                    0
prio                               :                  120
clock-delta                        :                  108

se.exec_start won't work because it's strictly increasing. se.sum_exec_runtime might work as it increases when it's running and plateaus when it's sleeping. The key is to measure how many milliseconds it plateaus: that period will be exactly what we need (plus an overhead of the scheduling plus an overhead of the inefficient computations in the program).
After some trial and error I whipped up some code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
#define SAMPLE_RATE 20
int main(int argc, char **argv)
{
	char *file_name=argv[1];
	char *line = NULL;
	size_t line_allocated = 0;
 
	int prev_time = 0;
	int current_time = 0;
	int count = 0;
	while(1) {
		FILE *f = fopen(file_name, "r");
 
		/* skip to 5th */
		getline( &line ,&line_allocated, f);
		getline( &line ,&line_allocated, f);
		getline( &line ,&line_allocated, f);
		getline( &line ,&line_allocated, f);
		getline( &line ,&line_allocated, f);
		sscanf(line, "se.sum_exec_runtime : %d", &current_time);
		fclose(f);
 
		if (current_time == prev_time) {
			count += SAMPLE_RATE;
		}
		else {
			if(count != 0)
				printf("%d\n", count);
			count = 0;
		}
		prev_time = current_time;
		usleep(SAMPLE_RATE * 1000);
	}
	if (line)
		free(line);
	return 0;
}

The formatted result was the following:

                                                            5440 4640 3060 1980 3960 4580 3660 3660 3060 580 4660 5260 4960 3060 580
4060 5360 3660 3060 1860 4040 5540 1460 4540 3660 3080 1960 5440 4660 3080 1980 3940 4960 3660 3660 3060 580 4660 5240 4940 3060 580
4040 5340 3660 3060 1860 4080 5540 1460 4560 3620 3060 1960 5440 4640 3060 1960 3960 4960 3660 3660 3060 580 4640 5240 4940 3060 580
4060 5360 3660 3060 1860 4060 5560 1480 4560 3660 3060 1960 5440 4080 3060 1980 3960 4960 3660 3660 3060 560 4660 5260 4960 3060 580

Jackpot! The pattern repeats, there are minor variations but hopefully the answer is legible. We just need to transform this back to ASCII.

#!/usr/bin/python
import sys
inp = open( sys.argv[1]  , "rt")
line = inp.readline()
while line:
	try:
		msec = int(line)
		sys.stdout.write( chr(msec/100 + 0x41) )
	except:
		True
	line = inp.readline()
$ ./decode.py sleep_pattern
wo_Thnee_Four_Five_SixOne_Two_Three_Four_Five_SixOne_Two_Three_Four_Five_SixOne_Twi_Three_Four_F

So the flag is “One_Two_Three_Four_Five_Six”.

writeups/hypercomputer2.txt · Last modified: 2014/04/23 07:17 by vladum
[unknown link type]Back to top