首页
社区
课程
招聘
3
[原创]CTF php .so pwn 题型分析
发表于: 2024-6-4 13:18 20104

[原创]CTF php .so pwn 题型分析

2024-6-4 13:18
20104

2024-05-28-PHP-So-Pwn

基础的一些知识

  • 最近打比赛 遇到了几道 php so 模块的 pwn 题,个人感觉挺有意思的

  • 一般情况下会把php 大部分函数 ban 掉,只是用 自定义so 文件里的函数

.so 文件的导出函数

虽然里面函数名前面有zif_​ ,实际上调用的函数名是

1
2
3
4
5
add_chunk()
show_chunk()
edit_chunk()
edit_name()
free_chunk()

image

gdb调试方法(一种)

  • 可以先把本地的 gdbserver 上次到docker, 能正常运行就行了

image

  • 我只开了一个 docker 环境,正常情况下,他的ip 就是 这个加1 ,也就是 172.17.0.2

image

image

  • 然后用 gdbserver 启动

image

image

  • 上面是调试的方法,

函数传参分析

  • 分析一下正常情况的状态
1
2
3
4
5
6
7
8
 
echo "123";
echo "123";
 
add_chunk(1,[0x23],"name");
 
?>
  • gdb 调试
1
2
rdi 第一个参数可能是 传递参数的数量,
rsi 可以通过该指针指向的字符数量来得知正确的参数数量(当前add_chunk) `lzz` 3个参数 类型

image

  • 然后后面参数里的地址应该是用于存放我们传入参数的指针

image

zend_parse_parameters

https://www.bookstack.cn/read/phpbook/7.1.md

最简单的获取函数调用者传递过来的参数便是使用zend_parse_parameters()函数。zend_parse_parameters()函数的前几个参数我们直接用内核里宏来生成便可以了,形式为:ZEND_NUM_ARGS() TSRMLS_CC,注意两者之间有个空格,但是没有逗号。从名字可以看出,ZEND_NUM_ARGS()代表着参数的个数。紧接着需要传递给zend_parse_parameters()函数的参数是一个用于格式化的字符串,就像printf的第一个参数一样。下面表示了最常用的几个符号。

  • 数字类型判断
1
2
3
4 int
6 strings
7 arrary

  • 类型 传擦和返回值,用于恢复结构
1
int zend_parse_parameters(size_t arg_num, char *TypeChar, ...)
  • 如果参数不是预期的数量和类型,zend_parse_parameters​ 会返回 -1 否则 0

  • 简单了解下 lzz​ 是什么东东

1
2
3
4
5
6
7
8
9
10
11
b Boolean
l Integer 整型
d Floating point 浮点型
s String 字符串
r Resource 资源
a Array 数组
o Object instance 对象
O Object instance of a specified type 特定类型的对象
z Non-specific zval 任意类型~
Z zval**类型
f 表示函数、方法名称,PHP5.3之前没有的
  • 然后是简单分析的图

image

  • 这部分来自星盟安全的pwnshell wp

bin_data_size的映射表,将宏定义展开为如下数组所示:

1
uint32_t bin_data_size[] = {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048, 2560, 3072 ...};

  • 接下来来分析几道题目

NumberGame

  • 来自 第一届“长城杯”信息安全铁人三项赛决 夺取闯关 pwn numbergame

题目环境

  • server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3
 
import sys
import tempfile
import os
 
sys.stdout.write("File size >> ")
sys.stdout.flush()
size = int(sys.stdin.readline().strip())
if size > 1024*1024:
    sys.stdout.write("Too large!")
    sys.stdout.flush()
    sys.exit(1)
sys.stdout.write("Data >> ")
sys.stdout.flush()
script = sys.stdin.read(size)
 
filename = tempfile.mktemp()
with open(filename, "w") as f:
    f.write(script)
 
os.system("php " + filename)
  • Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FROM php:8.3-apache
RUN apt-get update
RUN apt-get upgrade -y
RUN DEBIAN_FRONTEND=noninteractive
RUN apt install lib32z1 xinetd libstdc++6 lib32stdc++6 python3 -y
 
RUN useradd -m ctf
RUN echo "ctf:ctf" | chpasswd
WORKDIR /home/ctf
ADD ynetd /home/ctf
ADD server.py /home/ctf
ADD run.sh /home/ctf
 
COPY ./php.ini /usr/local/etc/php
COPY ./numberGame.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831
COPY ./readflag /
COPY ./flag.txt /flag.txt
 
RUN chmod 400 /flag.txt
RUN chmod u+sx /readflag
 
 
EXPOSE 5555
CMD ./ynetd -p 5555 "timeout 30 ./run.sh"
  • 然后 php.ini​ 基本上禁用了所有的php函数
[PHP]

disable_functions = "system",... 
disable_classes = ""...
extension = numberGame.so ; 扩展的 so

  • 给了 .tar.gz​ 的docker 镜像包,使用以下命令进行加载(线下比赛没有网络 给Dockerfile​ 也拉起不了,所以给打包的环境)
1
docker load -i jingxiang.tar.gz

image

  • 启动环境
1
docker run --name run_numbergame  -p 5555:5555 -itd numbergame:latest

分析 numberGame.so 漏洞

imageimage

image

image

触发漏洞

大致结构体

1
2
3
4
5
6
struct mechunk{
    size_t *name_ptr;
    size_t num;
    size_t array_size; // 默认 最大 100, 通过漏洞使这里变大,我们既可以实现数组越界
     ...;
};

正常情况下,array_size 是 4


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 3
支持
分享
赞赏记录
参与人
雪币
留言
时间
apwnaroot
+1
你的帖子非常有用,感谢分享!
2024-8-11 20:18
PLEBFE
看雪因你而更加精彩!
2024-6-21 02:11
Tokameine
谢谢你的细致分析,受益匪浅!
2024-6-6 21:24
最新回复 (0)
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册