<?php
/**
 * Deobfuscator — giải mã các lớp obfuscation phổ biến
 */
if (!defined('ABSPATH')) exit;

class Foxblock_Deobfuscator
{
    /** Số lần đệ quy tối đa để tránh vòng lặp vô hạn */
    const MAX_DEPTH = 4;

    /**
     * Cố gắng giải mã một chuỗi qua nhiều lớp obfuscation
     * Trả về chuỗi đã giải mã hoặc null nếu không giải được
     */
    public static function decode(string $value, int $depth = 0): ?string
    {
        if ($depth >= self::MAX_DEPTH) return null;
        if (strlen($value) < 8) return null;

        // Thử base64
        $decoded = self::try_base64($value);
        if ($decoded !== null) {
            // Đệ quy nếu kết quả vẫn còn obfuscated
            $deeper = self::decode($decoded, $depth + 1);
            return $deeper ?? $decoded;
        }

        // Thử gzinflate(base64)
        $decoded = self::try_gzinflate($value);
        if ($decoded !== null) {
            $deeper = self::decode($decoded, $depth + 1);
            return $deeper ?? $decoded;
        }

        // Thử str_rot13
        $decoded = self::try_rot13($value);
        if ($decoded !== null) {
            $deeper = self::decode($decoded, $depth + 1);
            return $deeper ?? $decoded;
        }

        return null;
    }

    /**
     * Giải mã base64 — chỉ chấp nhận nếu kết quả là text hợp lệ
     */
    public static function try_base64(string $value): ?string
    {
        $value = trim($value);
        // Phải đủ dài và đúng ký tự base64
        if (!preg_match('/^[A-Za-z0-9+\/]{20,}={0,2}$/', $value)) return null;

        $decoded = @base64_decode($value, true);
        if ($decoded === false) return null;

        // Kết quả phải là printable text (không phải binary)
        if (!mb_check_encoding($decoded, 'UTF-8')) return null;
        if (strlen($decoded) < 10) return null;

        return $decoded;
    }

    /**
     * Giải mã gzinflate(base64_decode(...))
     */
    public static function try_gzinflate(string $value): ?string
    {
        $b64 = self::try_base64($value);
        if ($b64 === null) return null;

        $inflated = @gzinflate($b64);
        if ($inflated === false) {
            $inflated = @gzuncompress($b64);
        }
        if ($inflated === false) return null;
        if (!mb_check_encoding($inflated, 'UTF-8')) return null;

        return $inflated;
    }

    /**
     * Giải mã str_rot13 — chỉ chấp nhận nếu kết quả có từ khóa PHP
     */
    public static function try_rot13(string $value): ?string
    {
        $decoded = str_rot13($value);
        // Kết quả phải chứa ký tự PHP phổ biến để tránh false positive
        if (preg_match('/\$|function|eval|base64|include|require/i', $decoded)) {
            return $decoded;
        }
        return null;
    }

    /**
     * Giải mã chuỗi chr() concatenation: chr(101).chr(118).chr(97)...
     */
    public static function decode_chr_concat(string $code): ?string
    {
        if (!preg_match('/chr\s*\(\s*\d+\s*\)/', $code)) return null;

        $result = preg_replace_callback(
            '/chr\s*\(\s*(\d+)\s*\)/',
            function ($m) { return chr((int)$m[1]); },
            $code
        );

        // Xóa dấu nối chuỗi
        $result = preg_replace('/\s*\.\s*/', '', $result ?? '');
        return strlen($result) > 3 ? $result : null;
    }

    /**
     * Giải mã hex string: \x65\x76\x61\x6c
     */
    public static function decode_hex_string(string $value): ?string
    {
        if (!preg_match('/(?:\\\\x[0-9a-fA-F]{2}){4,}/', $value)) return null;

        $decoded = preg_replace_callback(
            '/\\\\x([0-9a-fA-F]{2})/',
            function ($m) { return chr(hexdec($m[1])); },
            $value
        );

        return $decoded ?? null;
    }
}
