[write-up] hack.lu - PHPGolf

"Hack.lu is an open convention/conference where people can discuss about computer security, privacy, information technology and its cultural/technical implication on society"
The 2015-edition of the "hack.lu" conference took place from 20-22 October 2015 in Luxembourg. Part of the conference was a capture the flag (CTF) that could be attended both online and on-site.
I decided to participate together with the folks from the AGRS (TU-Berlin).

PHP Golf Challenge



This was the task description:

Johnny B. Krad from your local schoolyard gang still thinks that you are a poser! Even if you could beat him in Perl golf, you probably can't in PHP golf...

Link: https://school.fluxfingers.net:1521/golf/php/


Visiting the link we got some more information:

Goal
Write a PHP program that takes a parameter as input and outputs a filtered version with alternating upper/lower case letters of the (english) alphabet. Non-letter characters have to be printed but otherwise ignored.

Example
Input Hello World! Hallo Welt!
Output HeLlO wOrLd! HaLlO wElT!

Rules
  You have 1 second.
  You have 62 (ASCII) chars.
  Do not flood the server.


So let's summarize:
  - we have to come up with a pretty short php script (<=62 chars)
  - the code should run relatively fast
  - we have to alternate the case of the input letters, skipping non-letter chars

Development of the solution



I started by writing the whole thing down in pure PHP. I tried to keep it short but also in a way that I knew would work:
<?php for($a=$argv[1];$b=$a[$i++];print preg_match('/(\w)/',$b)?($g++%2?lcfirst($b):ucfirst($b)):$b)?>

We have a for loop that iterates over the string we reveive from argv[1]. We count the number of chars processed to alternate between upper and lowercase for every other character. We use the preg_match function to process only letter characters*.

* This matches underscores also but I figured out that there were samples that didn't include one. Bit of a dirty hack but I guess that is what you got to do sometimes ;-).

The problem with this was that it was still 102 characters which is way to much.
I did two more attempts (actually many more but I will just show these two):
<?php for(;$b=$argv[1][$i++];print preg_match('/\w/',$b)?($i%2?lcfirst($b):ucfirst($b)):$b)?>
<?php print preg_replace('/(\w)/',$i++%2?lcfirst('$1'):ucfirst('$1'),$argv[1])?>


That got me down to 80 characters but that was still way to much.
Looking at the code and knowing that php is pretty strict with its syntax I assumed that I had to go another route. My first thought was to call exec with a perl command and the solution of the "Perl Golf" challenge that we already solved but that was still too long.
That is why I settled for a solution that called the exec function and executed two shell commands.

<?php exec("echo $argv[1]".'|sed -r "s/(\w)(\W*\w?)/\u\1\L\2/g">&2')?>
Characters: 70

We echo argv[1] into the sed command which matches a letter-character, multiple optional non-letter characters and a single optional letter-character "(\w)(\W*\w?)".
This provides us with the functionality to alternate between the cases.
We use the "\u" to convert to uppercase and "\L" to convert the match to lowercase.

I then found out that there is a shorthand for the echo command. We could just call "=exec()" which reduced the payload further:
<?=exec("echo $argv[1]|sed -r 's/(\w)(\W*\w?)/\u\\1\L\\2/g'")?>
Characters: 63

The final character was chopped off by removing the "?>" and replacing it with a semicolon.
<?=exec("echo $argv[1]|sed -r 's/(\w)(\W*\w?)/\u\\1\L\\2/g'");
Characters: 62


Input of are! Being you! within create in world, and spiritual? help!
Output Of ArE! bEiNg YoU! wItHiN cReAtE iN wOrLd, AnD sPiRiTuAl? HeLp!
Expected Of ArE! bEiNg YoU! wItHiN cReAtE iN wOrLd, AnD sPiRiTuAl? HeLp!
Here's the flag: flag{perlgolfisbetter}



Denis Werner
26.10.2015

zurück