【Pwn-长城杯2025 (php-pwn) simi-final php-master 详解】此文章归类为:Pwn。
我们拿到附件之后可以看到文件的结构大致如下
由于之前也没有接触过phppwn,所以比赛现场看到这个文件结构还是很懵逼的
赛后拿去问问gpt
==以下是GPT的回答:==
这个文件的结构看起来非常像是一个 OCI(Open Container Initiative)镜像,也就是说,它很可能是一个用来打包容器镜像的标准格式结构。下面是每个文件或目录的作用解析:
这个 .tar
文件不是普通的 PHP 项目源码压缩包,而是一个 容器镜像文件(OCI 镜像),它可能是使用类似 buildah
、skopeo
、docker
(带有 --format oci
参数)等工具打包生成的。
经过gpt分析,这是一个docker打包好的容器镜像文件,所以可以通过docker启动,并进入docker容器查看具体的文件结构
通过命令可以直接docker 加载这个镜像,然后运行进入,就可以查看这个容器内大致的文件结构了
同kernel那类题目有些相似,一般php pwn都是会在一个extensions的文件夹下放入一个.so的动态链接库文件,pwn手分析的目标一般都是这些.so文件,出题人留下的漏洞一般都写在这些动态链接库当中
本题也不例外,我们可以用
查找extensions目录所在的位置,可以在这个目录下找到我们需要分析的.so动态链接库文件
找到了需要分析的文件,我们如何从镜像中将之提取出来方便我们本机分析呢?
我们用到docker-compose 组件,创建一个yml启动配置文件,建立本机与容器之间的映射关系,就可以在本机与docker容器之间建立一个类似共享文件夹的东西,在容器中将这些.so文件复制到 这个共享文件夹中,就可以拉出来正常分析了
docker-compose.yml
我们只需要在容器中将这些.so文件 cp 到 /var/www/html/exp文件夹下,就可以在本机的data文件下看到了,也就可以拿去ida分析了
index.php
我们的题目是一个apache启动的index.php,可以做一个任意文件上传,我们应该像web题一样上传一个可以执行的.php文件并访问他执行,这里也就顺便把 fix 说了加个后缀过滤,不让上传php文件就完事了。。。
所以exp也得用php写了
题目这里给的信息已经很明显了,直接取名vuln.so明显有问题
打开看到的信息大致如下,出题人还是很友好的,符号表全部都有保留方便逆向分析
在解析具体函数之前介绍一下
???? 函数原型
参数说明:
常见类型字符
还原结构体,大致如下
同时汇编观察发现,zend_parse_parameters 函数的传入参数其实很多,修正一下
这下对劲了
基本的需要利用的漏洞函数都已解析完毕
注意到,本质是堆的UAF 任意地址写,同时overwrite函数没有检查init
从题目的docker容器中可以查找到对应的php版本
php --version
PHP 8.1.20 (cli) (built: Jun 13 2023 12:02:18) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.20, Copyright (c) Zend Technologies
下载它的源码
heap相关结构体
_zend_mm_heap
_zend_mm_chunk
注释写的还算比较好理解
emalloc
zend_mm_alloc_heap
对于php的堆利用大多数都采用小堆块,我们直接看小堆块的申请逻辑
zend_mm_alloc_small
zend_mm_alloc_small_slow
至此emalloc的流程大致的解析完毕
efree
和emalloc很像,我们直接跟到最后那个函数,看看他如何处理free掉的堆块
zend_mm_free_small
总体是十分简洁好看的,就是简单的链表头插
那么我们不难得出一个大体的结论和逻辑
emalloc
efree
直接头插链入freelist
而且好像没有做很多检查和限制,有点像tcache 的逻辑
而事实上,我们完全可以把它当作tcache 来攻击
在4. 中提到的端口映射,实际上我们只用将gdbserver传入类似共享文件夹中,然后在容器中启动监听,再让主机启动gdb target remote ip:port 即可调试上
用我们上文写好的启动配置 启动
可以清晰看到我们的端口映射关系和启动信息
在主机中访问本地ip,可以看到docker容器中的80端口已经被映射了出来,我们可以访问到这个服务了
我们在docker容器中传入gdbserver 并运行
直接 target remote 本机已经映射好的端口就可以远程调试上了
嫌麻烦的话可以写一个启动脚本
在写之前我们可以看看vuln.so的保护
[*] '/root/Desktop/pwn/php_master/data/vuln.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
got表可写
首先是套板子的介绍,我们打pwn都需要直到各种基址,这样题目中的vuln的got表偏移,对于我们而言才有的意义
只要没有禁用我们的读写权限,就可以读出基址
否则还是走老路,找一些show的函数,泄露基址
由yaml文件,我们的
用上文所讲的方法就可以连接到容器的gdbserver
接下来我们要先进行调试,看看我们的思路是否正确
由于做好了映射,我们直接本机访问127.0.0.1:1111 端口就可以触发exp.php了
但由于很多模块还未被加载到内存,所以让本机gdb先直接跑起来,c
此时会加载一堆符号表,同时将.so文件加载到内存
CTRL + c 打断
此时我们就可以对着vuln.so中的函数直接下断了
然后本机浏览器访问localhost:1111端口,即可触发exp.php
这里我直接下断allocate过程,如果一切顺利的话,第四次执行到该函数,会申请到efree@got
其他的如果读者还想验证也是同样的道理
第四次 allocate
返回值 可以观察到就是efree@got
将一个堆块内容改为字符串
将将efree@got改为system函数地址
最后调用clear函数,efree的触发system
输出我们的flag
一切如我们所愿
至此exp就算编写完成了
完结撒花
参考
cd8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4q4I4i4K6g2X3y4e0b7J5x3e0R3^5x3K6y4Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5@1x3o6f1J5z5o6t1K6z5l9`.`.
php拿基址和封装p64的板子拿的是csdn上这个佬的代码
特别鸣谢
感谢Tplus大佬的指点教学,给佬磕了
docker load -i php-master.
tar
docker run -it your_image_id
/bin/bash
docker load -i php-master.
tar
docker run -it your_image_id
/bin/bash
find . | grep
"extensions"
find . | grep
"extensions"
<?php
@
error_reporting
(E_ALL);
if
(
$_SERVER
[
'REQUEST_METHOD'
] ===
'POST'
) {
if
(isset(
$_FILES
[
'file'
])) {
$file
=
$_FILES
[
'file'
];
$upload_dir
=
''
;
$target_file
=
$upload_dir
.
basename
(
$file
[
'name'
]);
$result
= move_uploaded_file(
$file
[
'tmp_name'
],
$target_file
);
if
(
$result
) {
$message
=
'文件上传成功!'
;
$msg_class
=
'success'
;
}
else
{
$message
=
'文件上传失败'
;
$msg_class
=
'error'
;
}
}
else
{
$message
=
'没有选择要上传的文件'
;
$msg_class
=
'error'
;
}
}
?>
<!DOCTYPE html>
<html lang=
"zh-CN"
>
<head>
<meta charset=
"UTF-8"
>
<title>PHP MASTER</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
.upload-box {
border: 2px dashed #ccc;
padding: 30px;
text-align: center;
}
.btn {
background: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn:hover {
background: #0056b3;
}
.message {
padding: 15px;
margin: 20px 0;
border-radius: 4px;
}
.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
</style>
</head>
<body>
<h2>PHP MASTER</h2>
<?php
if
(isset(
$message
)): ?>
<div
class
=
"message <?php echo $msg_class; ?>"
>
<?php
echo
$message
; ?>
</div>
<?php
endif
; ?>
<form action=
""
method=
"post"
enctype=
"multipart/form-data"
class
=
"upload-box"
>
<p>请选择要上传的文件:</p>
<input type=
"file"
name=
"file"
required>
<br><br>
<button type=
"submit"
class
=
"btn"
>上传文件</button>
</form>
</body>
</html>
<?php
@
error_reporting
(E_ALL);
if
(
$_SERVER
[
'REQUEST_METHOD'
] ===
'POST'
) {
if
(isset(
$_FILES
[
'file'
])) {
$file
=
$_FILES
[
'file'
];
$upload_dir
=
''
;
$target_file
=
$upload_dir
.
basename
(
$file
[
'name'
]);
$result
= move_uploaded_file(
$file
[
'tmp_name'
],
$target_file
);
if
(
$result
) {
$message
=
'文件上传成功!'
;
$msg_class
=
'success'
;
}
else
{
$message
=
'文件上传失败'
;
$msg_class
=
'error'
;
}
}
else
{
$message
=
'没有选择要上传的文件'
;
$msg_class
=
'error'
;
}
}
?>
<!DOCTYPE html>
<html lang=
"zh-CN"
>
<head>
<meta charset=
"UTF-8"
>
<title>PHP MASTER</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
.upload-box {
border: 2px dashed #ccc;
padding: 30px;
text-align: center;
}
.btn {
background: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn:hover {
background: #0056b3;
}
.message {
padding: 15px;
margin: 20px 0;
border-radius: 4px;
}
.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
</style>
</head>
<body>
<h2>PHP MASTER</h2>
<?php
if
(isset(
$message
)): ?>
<div
class
=
"message <?php echo $msg_class; ?>"
>
<?php
echo
$message
; ?>
</div>
<?php
endif
; ?>
<form action=
""
method=
"post"
enctype=
"multipart/form-data"
class
=
"upload-box"
>
<p>请选择要上传的文件:</p>
<input type=
"file"
name=
"file"
required>
<br><br>
<button type=
"submit"
class
=
"btn"
>上传文件</button>
</form>
</body>
</html>
ZEND_API
int
zend_parse_parameters(
int
num_args, const char
*
type_spec, ...);
ZEND_API
int
zend_parse_parameters(
int
num_args, const char
*
type_spec, ...);
参数 | 说明 |
---|---|
num_args |
PHP 调用时实际传入参数的数量,一般用 ZEND_NUM_ARGS()
|
type_spec |
参数类型字符串,例如 "ll" 表示两个 long 类型 |
... |
用于接收解析结果的变量的地址(按顺序传入) |
字符 | 含义 | C 变量类型 |
---|---|---|
l |
long / int(整型) | zend_long |
d |
double(浮点型) | double |
s |
字符串(和长度) |
char * , size_t
|
b |
bool | zend_bool |
z |
zval *(通用) | zval * |
a |
数组 | zval * |
o |
对象 | zval * |
` | ` | 可选参数的分隔符 |
struct
_zend_mm_heap {
#if ZEND_MM_CUSTOM
int
use_custom_heap;
#endif
#if ZEND_MM_STORAGE
zend_mm_storage *storage;
#endif
#if ZEND_MM_STAT
size_t
size;
/* current memory usage */
size_t
peak;
/* peak memory usage */
#endif
zend_mm_free_slot *free_slot[ZEND_MM_BINS];
/* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
size_t
real_size;
/* current size of allocated pages */
#endif
#if ZEND_MM_STAT
size_t
real_peak;
/* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
size_t
limit;
/* memory limit */
int
overflow;
/* memory overflow flag */
#endif
zend_mm_huge_list *huge_list;
/* list of huge allocated blocks */
zend_mm_chunk *main_chunk;
zend_mm_chunk *cached_chunks;
/* list of unused chunks */
int
chunks_count;
/* number of allocated chunks */
int
peak_chunks_count;
/* peak number of allocated chunks for current request */
int
cached_chunks_count;
/* number of cached chunks */
double
avg_chunks_count;
/* average number of chunks allocated per request */
int
last_chunks_delete_boundary;
/* number of chunks after last deletion */
int
last_chunks_delete_count;
/* number of deletion over the last boundary */
#if ZEND_MM_CUSTOM
union
{
struct
{
void
*(*_malloc)(
size_t
);
void
(*_free)(
void
*);
void
*(*_realloc)(
void
*,
size_t
);
} std;
struct
{
void
*(*_malloc)(
size_t
ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void
(*_free)(
void
* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void
*(*_realloc)(
void
*,
size_t
ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
} debug;
} custom_heap;
HashTable *tracked_allocs;
#endif
};
struct
_zend_mm_heap {
#if ZEND_MM_CUSTOM
int
use_custom_heap;
#endif
#if ZEND_MM_STORAGE
zend_mm_storage *storage;
#endif
#if ZEND_MM_STAT
size_t
size;
/* current memory usage */
size_t
peak;
/* peak memory usage */
#endif
zend_mm_free_slot *free_slot[ZEND_MM_BINS];
/* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
size_t
real_size;
/* current size of allocated pages */
#endif
#if ZEND_MM_STAT
size_t
real_peak;
/* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
size_t
limit;
/* memory limit */
int
overflow;
/* memory overflow flag */
#endif
zend_mm_huge_list *huge_list;
/* list of huge allocated blocks */
zend_mm_chunk *main_chunk;
zend_mm_chunk *cached_chunks;
/* list of unused chunks */
int
chunks_count;
/* number of allocated chunks */
int
peak_chunks_count;
/* peak number of allocated chunks for current request */
int
cached_chunks_count;
/* number of cached chunks */
double
avg_chunks_count;
/* average number of chunks allocated per request */
int
last_chunks_delete_boundary;
/* number of chunks after last deletion */
int
last_chunks_delete_count;
/* number of deletion over the last boundary */
#if ZEND_MM_CUSTOM
union
{
struct
{
void
*(*_malloc)(
size_t
);
void
(*_free)(
void
*);
void
*(*_realloc)(
void
*,
size_t
);
} std;
struct
{
void
*(*_malloc)(
size_t
ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void
(*_free)(
void
* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void
*(*_realloc)(
void
*,
size_t
ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
} debug;
} custom_heap;
HashTable *tracked_allocs;
#endif
};
struct
_zend_mm_chunk {
zend_mm_heap *heap;
zend_mm_chunk *next;
zend_mm_chunk *prev;
uint32_t free_pages;
/* number of free pages */
uint32_t free_tail;
/* number of free pages at the end of chunk */
uint32_t num;
char
reserve[64 - (
sizeof
(
void
*) * 3 +
sizeof
(uint32_t) * 3)];
zend_mm_heap heap_slot;
/* used only in main chunk */
zend_mm_page_map free_map;
/* 512 bits or 64 bytes */
zend_mm_page_info map[ZEND_MM_PAGES];
/* 2 KB = 512 * 4 */
};
struct
_zend_mm_chunk {
zend_mm_heap *heap;
zend_mm_chunk *next;
zend_mm_chunk *prev;
uint32_t free_pages;
/* number of free pages */
uint32_t free_tail;
/* number of free pages at the end of chunk */
uint32_t num;
char
reserve[64 - (
sizeof
(
void
*) * 3 +
sizeof
(uint32_t) * 3)];
zend_mm_heap heap_slot;
/* used only in main chunk */
zend_mm_page_map free_map;
/* 512 bits or 64 bytes */
zend_mm_page_info map[ZEND_MM_PAGES];
/* 2 KB = 512 * 4 */
};
ZEND_API
void
* ZEND_FASTCALL _emalloc(
size_t
size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
#if ZEND_MM_CUSTOM
//如果采用自定义
if
(UNEXPECTED(AG(mm_heap)->use_custom_heap)) {
return
_malloc_custom(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}
#endif
//默认的php zend引擎管理器
return
zend_mm_alloc_heap(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}
更多【Pwn-长城杯2025 (php-pwn) simi-final php-master 详解】相关视频教程:www.yxfzedu.com