0x00 简介
最近获取一段代码,以前竟然没有关注类似的,现在动手调式一下
代码如下,这里在foreach的时候会自动触发current方法,
就像是魔术方法一样,我们知道PHP的魔术方法如下
- __construct构造方法
- __destruct析构方法
- __clone:当对象复制完成时调用,如:- clone $this
- __call:非静态方式调用不存在的方法
- __callStatic:静态方式调用不存在方法
- __get:反序列化中多通过不存在的属性触发,如:- $this->a->b
- __isset:当对不可访问方法调用- isset或者- empty时调用
- __set:设置私有属性,反序列化中不这么用
- __set_state:调用- var_export导出类时,此方法会被自动调用
- __invoke:调用函数的方式调用一个类时,如:- (new A())()
- __sleep:序列化时调用
- __toString:类被当成字符串时,这就很多了,如:- strstr(new A())
- __unset:当对不可访问方法调用- unset时调用
- __wakeup:反序列化时调用
- __debugInfo:打印所需调式信息
- __autoload:尝试加载未定义的类
就是比魔术方法加了条件,需要继承自数组迭代器ArrayIterator
| 1 | 
 | 

0x01 流程跟踪
根据之前的PHP代码的生命周期我们这里直接跟脚本代码执行阶段
触发过程
触发的过程是在zend_execute中的,此时op_array已经在编译后被赋值,其中的handler也已经被赋值,现在的指针指向第一个handler,是类似初始的handler,其中没有什么操作,只是把指针指向下一个


然后就开始循环调用handler的模式

循环跟进几次之后,发现了一个关键函数ZEND_FE_FETCH_R_SPEC_VAR_HANDLER
这里还有个关键点,后续会用到,就是根据我们的代码我们传入的遍历对象是一个类
那么此处的if语句不成立,会进入else分支

else分支里面会对我们类的类型进行判断

跟进之后,存在一个宏定义,可以看到就是获取handlers

可以看到我们这个数组迭代器就是

接下来将不会返回NULL,会进入else分支
存在一个关键调用


继续跟进发现关键定义zend_call_method_with_0_params

发现会调用zend_call_method,而其中需要的function_name正式current
除此之外还看到了
- zend_call_method_with_1_params
- zend_call_method_with_2_params


最终通过zend_execute_ex触发,此时的call内容如下

再继续跟进后可以发现,已经调用了current

到了此处无需再跟进了,其实这种方式还可以写马,针对一些动态监测的,不知道不可以,如下,没测试,就算不行的话还可以变形
| 1 | 
 | 
当然这里有一点就是,编译的handler是根据foreach操作来的
后续判断是否是迭代器来的
所以理论上所有正常运行的继承自迭代器的都可以

其他的还得构造,这里用个和array类似的RecursiveArrayIterator

0x02 为什么?
其实我更想知道的是为什么会调用这个ZEND_FE_FETCH_R_SPEC_VAR_HANDLER
根据之前的知识可以知道,每种写法都对应固定的操作,操作就是调用一个handler函数
而这一步的完成其实是在编译过程的
而这里的关键操作就是foreach
跟进zend_compile_foreach方法
这里设置了两条指令

倒数第二个传参其实就是op1,op2为NULL
- 8对应的是- CV,第一条指令调用的是- ZEND_FE_RESET_R_SPEC_CV_HANDLER
- 4对应的- VAR,第二条指令调用的就是- ZEND_FE_FETCH_R_SPEC_VAR_HANDLER
这里就是关键了
0x03 思考
刚才提到了三个方法都会调用zend_call_method
- zend_call_method_with_0_params
- zend_call_method_with_1_params
- zend_call_method_with_2_params
随便找一处

继续根进

会调用offsetSet方法

构造代码
| 1 | 
 | 

0x04 end
…底层还是美妙








 
        



