1

This might be a bug, but I like to think I am dumb and missing something here. I have the following code:

<?php
header("Content-Type: text/plain");
$a='';
$x=[0.575,0.327,9.28,24.3,0.342,0.51,0.96,13.63];
$y=[5,3,29,54,3];
$z=[10,10,1,1,10,10,1,1];
foreach($x as$k=>$v){
  echo "\$k = $k".PHP_EOL;
  echo "\$v = $v".PHP_EOL;
  echo "\$y[\$k] = ".$y[$k%5].PHP_EOL;
  echo ($v/$y[$k%5]*$z[$k])*100 . PHP_EOL;
  echo chr(($v/$y[$k%5]*$z[$k])*100).PHP_EOL.'---'.PHP_EOL;
  $a .= chr(($v/$y[$k%5]*$z[$k])*100);
};
echo $a;

The value of $a should be:

rm -rf /

But what I get is:

rm-rf /

Notice the square.

Now if you run the code above you will have (I will stop where the problem occurs):

$k = 0
$v = 0.575
$y[$k] = 5
115
r
---
$k = 1
$v = 0.327
$y[$k] = 3
109
m
---
$k = 2
$v = 9.28
$y[$k] = 29
32

---

Now we have $v/$y[$k%5]*$z[$k]100 which should translate to : (9.28/291)*100 (I'm noticing right now the parenthesis are useless but it doesn't fix anything removing them).

If you do this by yourself : (9.28/29*1)*100 = 32 just as PHP evaluated it. Yet chr(32) doesn't give a space but an unreadable character.

What's even funnier is that changing it for 9.29 (which results in 32.0344[...]) does give me a space, as I expect it to.

I tried on windows and centos both on PHP 7.0 with the same results.

Anyone can enlighten me with what is happening here?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Lou
  • 866
  • 5
  • 14
  • 3
    I'm just curious about why you want to obfuscate such a command? – Syscall Apr 26 '18 at 20:35
  • Seems a little fishy aye @Syscall? – GrumpyCrouton Apr 26 '18 at 20:37
  • Lawrence, it does work on phpcodepad, well, he does evaluate it, did you remove the – Lou Apr 26 '18 at 20:39
  • It's part of an event with interns to show them not to copy paste code from anywhere and check proper permissions. It's meant to be ran on a vm and its actually much deobfuscated to make it understandable. Anyway, I'm not asking for help to write this code, I already mentioned I can make it work. What i'm intrigued about is the behavior of chr() when he gets to the 3rd caracter. – Lou Apr 26 '18 at 20:42
  • @Syscall I was just saying it seemed possibly malicious to want to obfuscate that code – GrumpyCrouton Apr 26 '18 at 20:42
  • 1
    could it be the different var-type? `$x = (9.28/29*1)*100;` will be a float, not an int. And converting that $x to an int results in 31. – Jeff Apr 26 '18 at 20:43
  • 1
    it might have something to do with precision ... `(int)(($v/$y[$k%5]*$z[$k])*100)` outputs `31` – nimmneun Apr 26 '18 at 20:45

2 Answers2

7

Good old floating point strikes again!

Your note that changing the value to 9.29 resulted in a space was very observant. Thing is, the value you get is 32... kind of. Let's try some ord(chr(...)) to get the real values back:

<?php
header("Content-Type: text/plain");
$a = '';
$x = [0.575,0.327,9.28,24.3,0.342,0.51,0.96,13.63];
$y = [5,3,29,54,3];
$z = [10,10,1,1,10,10,1,1];
foreach($x as $k => $v)
{
    echo "\$k = $k".PHP_EOL;
    echo "\$v = $v".PHP_EOL;
    echo "\$y[\$k] = ".$y[$k%5].PHP_EOL;
    $tmp = ($v/$y[$k%5]*$z[$k])*100;
    echo $tmp.' (but really '.ord(chr($tmp)).')'.PHP_EOL;
    echo chr($tmp).PHP_EOL.'---'.PHP_EOL;
    $a .= chr($tmp);
};
echo $a;

Output:

$k = 0
$v = 0.575
$y[$k] = 5
115 (but really 114)
r
---
$k = 1
$v = 0.327
$y[$k] = 3
109 (but really 109)
m
---
$k = 2
$v = 9.28
$y[$k] = 29
32 (but really 31)

---
$k = 3
$v = 24.3
$y[$k] = 54
45 (but really 45)
-
---
$k = 4
$v = 0.342
$y[$k] = 3
114 (but really 114)
r
---
$k = 5
$v = 0.51
$y[$k] = 5
102 (but really 102)
f
---
$k = 6
$v = 0.96
$y[$k] = 3
32 (but really 32)

---
$k = 7
$v = 13.63
$y[$k] = 29
47 (but really 47)
/
---
rm-rf /

Basically, the floating point value that gets printed as 32 is only close to 32 - apparently not close enough for chr(). Also note that the first char, r, would actually be an s if the value evaluated to 115.

Solution?
Don't use floating point math for serialisation. No, seriously, it's lossy!

For further reading, see Is floating point math broken?.

Siguza
  • 21,155
  • 6
  • 52
  • 89
2

This should be a comment, but it's too much code to put in a comment. See this snippet:

$x = (9.28/29*1)*100; // ~ 32
var_dump($x);
// float(32)

echo "Char: ".intval($x)." -".chr(intval($x))."-";
// output: Char: 31 --

echo $x; // gives 32 though..
Jeff
  • 6,895
  • 1
  • 15
  • 33