PHP序列化,反序列化

一.什么是序列化和反序列化

php类与对象

类是定义一系列属性和操作的模板,而对象,就是把属性进行实例化,完事交给类里面的方法,进行处理。

<?php
class people{
   //定义类属性(类似变量),public 代表可见性(公有)
    public $name = 'joker';
   //定义类方法(类似函数)
   public function smile(){
        echo $this->name." is smile...\n";
   }
}

$psycho = new people(); //根据people类实例化对象
$psycho->smile();
?>

上述代码定义了一个people类,并在在类中定义了一个public类型的变量n a m e 和类方法 s m i l e 。然后实例化一个对象 name和类方法smile。然后实例化一个对象name和类方法smile。然后实例化一个对象psycho,去调用people类里面的smile方法,打印出结果。
含义


php序列化(serialize):是将变量转换为可保存或传输的字符串的过程
php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用
这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。
对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息“复刻”出一个和原来一模一样的对象。
常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。 
示例
 

<?php
class object{
    public $team = 'joker';
    private $team_name = 'hahaha';
    protected $team_group = 'biubiu';

    function hahaha(){
        $this->$team_members = '奥力给';
    }
}
$object = new object();
echo serialize($object);
?>

在这里插入图片描述

O 表示object,一个对象
6 表示对象名长度为6
object 对象名称
3 表示有3个属性
后面花括号里的是类属性的内容,s表示的是类属性team的类型,4表示类属性team的长度,后面的以此类推。
对象类型:对象长度:“对象名称”:类中变量的个数:{变量类型:长度:“名称”;类型:长度:“值”;…}
序列化
序列化格式中的字母含义:
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string

类方法并不会参与到实例化里面
需要注意的是变量受到不同修饰符(public,private,protected)修饰进行序列化时,序列化后变量的长度和名称会发生变化。使用public修饰进行序列化后,变量$team的长度为4,正常输出。
使用private修饰进行序列化后,会在变量$team_name前面加上类的名称,在这里是object,并且长度会比正常大小多2个字节,也就是9+6+2=17。
使用protected修饰进行序列化后,会在变量$team_group前面加上*,并且长度会比正常大小多3个字节,也就是10+3=13。
通过对比发现,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则:
1.受Private修饰的私有成员,序列化时: \x00 + [私有成员所在类名] + \x00 [变量名]
2. 受Protected修饰的成员,序列化时:\x00 + * + \x00 + [变量名]
其中,“\x00"代表ASCII为0的值,即空字节,” * " 必不可少。

 

反序列化

依次根据规则进行反向复原。
这边定义一个字符串,然后使用反序列化函数unserialize进行反序列化处理,最后使用var_dump进行输出:

<?php
    $ser = 'O:6:"object":3:{s:1:"a";i:1;s:4:"team";s:6:"hahaha";}';
    $ser = unserialize($ser);
    var_dump($ser);
?>

在这里插入图片描述

二.魔术方法

方法名作用
__construct构造函数,在创建对象时候初始化对象,一般用于对变量赋初值
__destruct析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用
__toString当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法
__wakeup()使用unserialize时触发,反序列化恢复对象之前调用该方法
__sleep()使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
__destruct()对象被销毁时触发
__call()在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
__callStatic()在静态上下文中调用不可访问的方法时触发
__get()读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性)
__set()在给不可访问属性赋值时,即在调用私有属性的时候会自动执行
__isset()当对不可访问属性调用isset()或empty()时触发
__unset()当对不可访问属性调用unset()时触发
__invoke()当脚本尝试将对象调用为函数时触发

__tostring的具体触发场景:
(1) echo(o b j ) / p r i n t ( obj) / 打印时会触发
(2) 反序列化对象与字符串连接时
(3) 反序列化对象参与格式化字符串时
(4) 反序列化对象与字符串进行比较时(PHP进行比较的时候会转换参数类型)

(5) 反序列化对象参与格式化SQL语句,绑定参数时
(6) 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
(7) 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用
(8) 反序列化的对象作为 class_exists() 的参数的时候


三.php反序列化漏洞(对象注入)
在反序列化过程中,其功能就类似于创建了一个新的对象(复原一个对象可能更恰当),并赋予其相应的属性值。如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。
挖掘反序列化漏洞的条件是:
1.代码中有可利用的类,并且类中有__wakeup(),__sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。
2. unserialize()函数的参数可控。

php对象注入示例一:

<?php
class A{
    var $test = "demo";
    function __destruct(){
        @eval($this->test);
    }
}
$test = $_POST['test'];
$len = strlen($test)+1;
$p = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
$test_unser = unserialize($p); // 反序列化同时触发_destruct函数
?>

 最终的目的是通过调用__destruct()这个析构函数,将恶意的payload注入,导致代码执行。根据上面的魔术方法的介绍,当程序跑到unserialize()反序列化的时候,会触发__destruct()方法,同时也可以触发__wakeup()方法。但是如果想注入恶意payload,还需要对$test的值进行覆盖,题目中已经给出了序列化链,很明显是对类A的$test变量进行覆盖。

在这里插入图片描述

首先这里要传入$test,而test就是phpinfo();$p = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; 这里重新定义了A,把test变成了我们传递的值,这句话的功能就相当于var $test = "$_POST=['test']";,而在php中这句话就把前面的var $test = "demo";覆盖掉了,从而执行了__destruct()这个析构函数,eval也就执行了我们提交的phpinfo
 

php对象注入示例二:

<?php
class test{
    var $test = '123';
    function __wakeup(){
        $fp = fopen("flag.php","w");
        fwrite($fp,$this->test);
        fclose($fp);
    }
}
$a = $_GET['id'];
print_r($a);
echo "</br>";
$a_unser = unserialize($a);
require "flag.php";
?>

这里主要通过调用魔术方法__wakeup将KaTeX parse error: Expected group after '_' at position 47: …ize()反序列化操作时会触发_̲_wakeup魔术方法,接下来…test变量的值,将php探针写入flag.php文件中,并通过下面的require引用,导致命令执行。
 

1.POP 面向属性编程(Property-Oriented Programing)

上面的例子都是基于 " 自动调用 " 的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过 " 自动调用 " 来达到目的了。这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,最终去构造出一个有杀伤力的payload。
 

POP链简介

1.POP 面向属性编程(Property-Oriented Programing)

常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的 " gadget " 找到漏洞点。
 

2. POP CHAIN

把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。
 

POP链利用技巧

1 一些有用的POP链中出现的方法:

命令执行:exec()、passthru()、popen()、system()
文件操作:file_put_contents()、file_get_contents()、unlink()
代码执行:eval()、assert()、call_user_func()

2. 反序列化中为了避免信息丢失,使用大写S支持字符串的编码。

PHP 为了更加方便进行反序列化 Payload 的 传输与显示(避免丢失某些控制字符等信息),我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示,使用如下形式即可绕过,即:s:4:"user"; -> S:4:"use\72";

3. 深浅copy

在php中如果我们使用 & 对变量A的值指向变量B,这个时候是属于浅拷贝,当变量B改变时,变量A也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。 $A = &$B;

4. 利用PHP伪协议

配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。

POP链构造案例

<?php
class main {
    protected $ClassObj;

    function __construct() {
        $this->ClassObj = new normal();
    }

    function __destruct() {
        $this->ClassObj->action();
    }
}

class normal {
    function action() {
        echo "hello bmjoker";
    }
}

class evil {
    private $data;
    function action() {
        eval($this->data);
    }
}
//$a = new main();
unserialize($_GET['a']);
?>

如上代码,危险的命令执行方法eval不在魔术方法中,在evil类中。但是魔术方法__construct()是调用normal类,__destruct()在程序结束时会去调用normal类中的action()方法。而我们最终的目的是去调用evil类中的action()方法,并伪造evil类中的变量KaTeX parse error: Expected group after '_' at position 41: …去构造POP利用链,让魔术方法_̲_construct()去调用…data赋予恶意代码,比如php探针phpinfo(),这样就相当于执行<?php eval("phpinfo();")?>`。尝试构造payload:
 

<?php
class main {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new evil();
    }
}
class evil {
    private $data = "phpinfo();";
}
$a = new main();
echo serialize($a);
?>

编写我们想要执行的效果,然后进行序列化。
但是由于$ClassObj是protected类型修饰,$data是private类型修饰,在序列化的时候,多出来的字节都被\x00填充,需要进行在代码中使用urlencode对序列化后字符串进行编码,否则无法复制解析。运行得到O:4:"main":1:{s:11:"*ClassObj";O:4:"evil":1:{s:10:"evildata";s:10:"phpinfo();";}}
在这里插入图片描述

PHP Session反序列化

session请求过程

当第一次访问网站时,Seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。
 

session_start的作用

当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie。
 

Session存储机制

PHP中的Session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。存储的文件是以sess_sessionid来进行命名的,文件的内容就是Session值的序列化之后的内容。
PHP Session在php.ini中主要存在以下配置项:
 

Directive*含义
session.save_handler设定用户自定义session存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)。默认为files
session.save_path设置session的存储路径,默认在/tmp
session.serialize_handler定义用来序列化/反序列化的处理器名字。默认使用php。
session.auto_start指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.upload_progress.enabed将上传文件的进度信息存储在session中。默认开启
session.upload_progress.cleanup一旦读取了所有的POST数据,立即清除进度信息。默认开启

在这里插入图片描述

注意:这里修改session.save.path在php.ini中修改,三个都要改而且要去掉前面的冒号,重启才可以生效 

在PHP中Session有三种序列化的方式,分别是php,php_serialize,php_binary,不同的引擎所对应的Session的存储的方式不同

存储引擎存储方式
php_binary键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数序列化处理的值
php键名 + 竖线 + 经过 serialize() 函数序列处理的值
php_serialize(PHP>5.5.4) 经过 serialize() 函数序列化处理的数组

Session反序列化漏洞

PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化,PHP中的Session的实现是没有的问题的,漏洞主要是由于使用不同的引擎来处理session文件造成的。
存在对$_SESSION变量赋值
php引擎存储Session的格式为
 

php键名 + 竖线 + 经过 serialize() 函数序列处理的值
php_serialize(PHP>5.5.4) 经过 serialize() 函数序列化处理的数组

如果程序使用两个引擎来分别处理的话就会出现问题。比如下面的例子,先使用php_serialize引擎来存储
session 1:

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['username'] = $_GET['user'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
?>

接下来使用php引擎来读取Session文件
session 2:

<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
class user{
    var $name;
    var $age;
    function __wakeup(){
        echo "hello ".$this->name." !"
    }
}
?>

漏洞的主要原因在于不同的引擎对于竖杠’ | ‘的解析产生歧义。
对于php_serialize引擎来说’ | ‘可能只是一个正常的字符;但对于php引擎来说’ | ‘就是分隔符,前面是$_SESSION['username']的键名 ,后面是GET参数经过serialize序列化后的值。从而在解析的时候造成了歧义,导致其在解析Session文件时直接对’ | ‘后的值进行反序列化处理。
可能有的人看到这里会有疑问,在使用php引擎读取Session文件时,为什么会自动对’ | '后面的内容进行反序列化呢?也没看到反序列化unserialize函数。
这是因为使用了session_start()这个函数 ,PHP会自动反序列化数据并填充$_session超级全局变量。PHP能自动反序列化数据的前提是,现有的会话数据是以特殊的序列化格式存储。
构造payload:
 

<?php
    class user{
        var $name;
        var $age;
    }
    $a = new user();
    $a->name = "bmjoker";
    $a->age = "888";
    echo serialize($a);
?>

运行得到O:4:"user":2:{s:4:"name";s:7:"bmjoker";s:3:"age";s:3:"888";},生成的payload如果想利用php引擎读取Session文件时对’ | ‘解析产生的反序列化漏洞,需要在payload前加个’ | ',这个时候经过php_serialize引擎存储就会变成:

在这里插入图片描述 

当使用php引擎的时候,php引擎会以|作为key和value的分隔符;左边部分是session的key,右边部分为session的vale
直接运行session 2 

在这里插入图片描述

 

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

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

相关文章

3.2 防火墙

数据参考&#xff1a;CISP官方 目录 防火墙基础概念防火墙的典型技术防火墙企业部署防火墙的局限性 一、防火墙基础概念 防火墙基础概念&#xff1a; 防火墙&#xff08;Firewall&#xff09;一词来源于早期的欧式建筑&#xff0c;它是建筑物之间的一道矮墙&#xff0c;用…

31 对集合中的字符串,按照长度降序排列

思路&#xff1a;使用集合的sort方法&#xff0c;新建一个Comparator接口&#xff0c;泛型是<String>&#xff0c;重写里面的compare方法。 package jiang.com; import java.util.Arrays; import java.util.Comparator; import java.util.List;public class Practice4 {…

SQL Server数据库如何添加mysql链接服务器(Windows系统)

SQL Server数据库如何添加mysql链接服务器&#xff08;Windows系统&#xff09; 一、说明二、下载mysql的odbc驱动三、安装mysql odbc四、配置ODBC4.1 控制面板→ODBC数据源&#xff08;64位&#xff09;→双击打开4.2 添加msql odbc数据源 五、测试添加是否成功六、打开SSMS&a…

基于YOLOv7开发构建MSTAR雷达影像目标检测系统

MSTAR&#xff08;Moving and Stationary Target Acquisition and Recognition&#xff09;数据集是一个基于合成孔径雷达&#xff08;Synthetic Aperture Radar&#xff0c;SAR&#xff09;图像的目标检测和识别数据集。它是针对目标检测、机器学习和模式识别算法的研究和评估…

python爬虫3:requests库-案例1

python爬虫3&#xff1a;requests库-案例1 前言 ​ python实现网络爬虫非常简单&#xff0c;只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点&#xff0c;方便以后复习。 申明 ​ 本系列所涉及的代码仅用于个人研究与讨论&#xff0c;并不会对网…

ubuntu上安装mosquitto服务

1、mosquitto是什么 Mosquitto 项目最初由 IBM 和 Eurotech 于 2013 年开发&#xff0c;后来于 2016 年捐赠给 Eclipse 基金会。Eclipse Mosquitto 基于 Eclipse 公共许可证(EPL/EDL license)发布&#xff0c;用户可以免费使用。作为全球使用最广的 MQTT 协议实现之一 &#x…

MySQL游标(二十九)

二八佳人体似酥&#xff0c;腰悬利剑斩愚夫&#xff0c;虽然不见人头落,暗里教君骨髓枯。 上一章简单介绍了MySQL流程控制(二十八) ,如果没有看过,请观看上一章 一. 游标 一.一 什么是游标 虽然我们也可以通过筛选条件 WHERE 和 HAVING&#xff0c;或者是限定返回记录的关键…

自动化测试CSS元素定位

目录 1.1 CSS定位 1.1.1 绝对路径定位 1.1.2 相对路径定位 1.1.3 类名定位 1.1.4 属性定位 1.1.4.1 ID属性定位 1.1.4.2 其他属性定位 1.1.4.3 模糊属性定位 1.1.5 子页面元素查找 1.1.6 伪类定位 1.1 CSS伪类 1.1 CSS定位 1.1.1 绝对路径定位 目标 查找第一个文…

任务 13、MidJourney种子激发极致创作,绘制震撼连贯画作

13.1 任务概述 通过本次实验任务&#xff0c;学员将深入了解Midjourney种子的概念和重要性&#xff0c;以及种子对生成图像的影响。他们将学会在Midjourney平台中设置种子值并调整其参数&#xff0c;以达到所需的效果。此外&#xff0c;任务还详细介绍了Midjourney V4.0版本中…

36.利用解fgoalattain 有约束多元变量多目标规划问题求解(matlab程序)

1.简述 多目标规划的一种求解方法是加权系数法&#xff0c;即为每一个目标赋值一个权系数&#xff0c;把多目标模型转化为一个单目标模型。MATLAB的fgoalattain()函数可以用于求解多目标规划。 基本语法 fgoalattain()函数的用法&#xff1a; x fgoalattain(fun,x0,goal,weig…

acwing第 115 场周赛第二题题解:维护最大值和次大值

一、链接 5132. 奶牛照相 二、题目 约翰的农场有 nn 头奶牛&#xff0c;编号 1∼n1∼n。 其中&#xff0c;第 ii 头奶牛的宽度为 wiwi&#xff0c;高度为 hihi&#xff0c; 有一天&#xff0c;它们聚餐后决定拍照留念。 关于拍照的描述如下&#xff1a; 它们一共拍了 nn…

2020年12月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题 第1题 执行语句print(1010.0)的结果为&#xff1f; A&#xff1a;10 B&#xff1a;10.0 C&#xff1a;True D&#xff1a;False 正确的答案是 C&#xff1a;True。 解析&#xff1a;在Python中&#xff0c;比较运算符 “” 用于比较两个值是否相等。在这个特…

[Qt]FrameLessWindow实现调整大小、移动弹窗并具有Aero效果

说明 我们知道QWidget等设置了this->setWindowFlags(Qt::FramelessWindowHint);后无法移动和调整大小&#xff0c;但实际项目中是需要窗口能够调整大小的。所以以实现FrameLess弹窗调整大小及移动弹窗需求&#xff0c;并且在Windows 10上有Aero效果。 先看一下效果&#xf…

java单例模式(详)

单例模式的应用场景 单例模式的优点 饿汉懒汉 1.所谓单例模式&#xff0c;就是采取一定个方法保证整个软件系统中&#xff0c;对某个类只能存在一个对象实例。 2.实现&#xff1a;饿汉式&#xff0c;懒汉式 3.区分懒汉式和饿汉式 饿汉式&#xff1a;坏处&#xff1a;加载时间过…

【ArcGIS Pro二次开发】(58):数据的本地化存储

在做村规工具的过程中&#xff0c;需要设置一些参数&#xff0c;比如说导图的DPI&#xff0c;需要导出的图名等等。 每次导图前都需要设置参数&#xff0c;虽然有默认值&#xff0c;但还是需要不时的修改。 在使用的过程中&#xff0c;可能会有一些常用的参数&#xff0c;希望…

HBase-组成

client 读写请求HMaster 管理元数据监控region是否需要进行负载均衡&#xff0c;故障转移和region的拆分RegionServer 负责数据cell的处理&#xff0c;例如写入数据put&#xff0c;查询数据get等 拆分合并Region的实际执行者&#xff0c;由Master监控&#xff0c;由regionServ…

Benchmarking Augmentation Methods for Learning Robust Navigation Agents 论文阅读

论文信息 题目&#xff1a;Benchmarking Augmentation Methods for Learning Robust Navigation Agents: the Winning Entry of the 2021 iGibson Challenge 作者&#xff1a;Naoki Yokoyama, Qian Luo 来源&#xff1a;arXiv 时间&#xff1a;2022 Abstract 深度强化学习和…

研发工程师玩转Kubernetes——emptyDir

kubernets可以通过emptyDir实现在同一Pod的不同容器间共享文件系统。 正如它的名字&#xff0c;当Pod被创建时&#xff0c;emptyDir卷会被创建&#xff0c;这个时候它是一个空的文件夹&#xff1b;当Pod被删除时&#xff0c;emptyDir卷也会被永久删除。 同一Pod上不同容器之间…

STM32 CubeMX USB_CDC(USB_转串口)

STM32 CubeMX STM32 CubeMX 定时器&#xff08;普通模式和PWM模式&#xff09; STM32 CubeMX一、STM32 CubeMX 设置USB时钟设置USB使能UBS功能选择 二、代码部分添加代码实验效果 ![请添加图片描述](https://img-blog.csdnimg.cn/a7333bba478441ab950a66fc63f204fb.png)printf发…

如何使用 ChatGPT 规划家居装修

你正在计划家庭装修项目&#xff0c;但不确定从哪里开始&#xff1f;ChatGPT 随时为你提供帮助。从集思广益的设计理念到估算成本&#xff0c;ChatGPT 可以简化你的家居装修规划流程。在本文中&#xff0c;我们将讨论如何使用 ChatGPT 有效地规划家居装修&#xff0c;以便你的项…