0%

前言

php pwn 泛指利用 PHP 环境下的漏洞进行 PWN(即:利用二进制层面的漏洞进行提权、RCE等)
本文主要针对 php extension 进行 pwn 利用

前置知识

php 扩展一般采用 c 语言编写
php 通过加载库文件(.so / .dll)来实现扩展
因为扩展函数是在 php 内部调用的,我们不能像平常那样直接执行 system one_gadget,这里通常是需要采用 popen 或者 exec 函数族(php 函数)来进行执行 bash命令来反弹 shell
php 扩展中使用 printf system 等函数会产生 stdout 输出, 但是这些输出有可能会被日志捕获被丢弃等, 如果我们想得到 php 扩展的输出我们需要使用 php 提供的函数比如 php_printf

简介

fastbin attack 指针对 ptmalloc2 中 fastbin 结构的攻击和漏洞利用手法

利用前提:

  • 存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞
  • 漏洞发生于 fastbin 类型的 chunk 中

根据利用手法可分为以下几类

前言

在栈的利用中, 我们通过劫持程序的返回地址来实现劫持程序的控制流
要想通过堆来进行漏洞利用, 可能会想到通过在堆上写入 shellcode 的方式然后劫持返回地址到堆上
但是程序往往会开始 NX (堆栈不可执行) 保护导致利用失败
而且程序可能根本没有缓冲区溢出漏洞
__malloc_hook__free_hook 正是为了应对这种情况的,与栈无关的堆劫持控制流的手法

从零开始的 Pwn 之旅 —— Off_by_one

漏洞成因

1. 循环条件计算错误

1
2
3
for (i = 0; i <= 10; i++) {
    // 循环体
}

上述代码实际上会执行 11 次循环。开发人员本意可能只想执行 10 次循环,实际上却多执行了一次,容易导致数组或缓冲区 越界

示例

ezheap

经典菜单题

1
2
3
4
5
6
7
~/D/ezheap ❯❯❯ ./ezheap
Easy Note.
1.Add.
2.Delete.
3.Show.
4.Edit.
Choice:

add 函数

 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
int add()
{
  __int64 v0; // rbx
  __int64 v1; // rax
  int v3; // [rsp+0h] [rbp-20h]
  int v4; // [rsp+4h] [rbp-1Ch]

  puts("Input your idx:");
  v3 = getnum();
  puts("Size:");
  v4 = getnum();
  if ( (unsigned int)v4 > 0x100 )
  {
    LODWORD(v1) = puts("Invalid!");
  }
  else
  {
    heaplist[v3] = malloc(0x20u);
    if ( !heaplist[v3] )
    {
      puts("Malloc Error!");
      exit(1);
    }
    v0 = heaplist[v3];
    *(_QWORD *)(v0 + 16) = malloc(v4);
    *(_QWORD *)(heaplist[v3] + 32LL) = &puts;
    if ( !*(_QWORD *)(heaplist[v3] + 16LL) )
    {
      puts("Malloc Error!");
      exit(1);
    }
    sizelist[v3] = v4;
    puts("Name: ");
    if ( !(unsigned int)read(0, (void *)heaplist[v3], 0x10u) )
    {
      puts("Something error!");
      exit(1);
    }
    puts("Content:");
    if ( !(unsigned int)read(0, *(void **)(heaplist[v3] + 16LL), sizelist[v3]) )
    {
      puts("Error!");
      exit(1);
    }
    puts("Done!");
    v1 = heaplist[v3];
    *(_DWORD *)(v1 + 24) = 1;
  }
  return v1;
}

detele 函数

bins 结构


fastbin

  • 结构:单向链表
  • 来源:free 时直接进入 fastbin
  • 用途:快速分配和释放小块内存
  • chunk size 范围
    • 通常为 0x20 ~ 0x80(包含头部,具体范围随 glibc 版本变化,部分新版本为到 0x70
    • size 字段实际为 chunk_size + 标志位
  • 特点
    • 释放后立即进入 fastbin,不合并相邻空闲块
    • 链表长度无限制(不是 7,7 是 tcache 的最大数目)
    • 只支持单向链表(每个 chunk 的 fd 指向下一个 chunk)

tcache

  • 结构:单向链表
  • 来源:free 时直接进入 tcache(优先于 fastbin)
  • 用途:线程本地缓存,加速小块内存分配和释放
  • chunk size 范围
    • 仅支持小于等于 0x410(1040字节)的 chunk(包含头部,实际范围依 glibc 版本不同)
    • size 字段为 chunk_size + 标志位
  • 特点
    • 每个 size 类别最多存储 7 个 chunk(超出则进入 unsorted bin)
    • 不合并相邻空闲块
    • 只支持单向链表(每个 chunk 的 fd 指向下一个 chunk)
    • 属于线程本地数据结构,各线程 tcache 独立,提升多线程性能

unsorted bin

  • 结构:双向链表(fd/bk)
  • 来源
    • 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
    • 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。关于 top chunk 的解释,请参考下面的介绍。
    • 当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。
  • 用途:存放刚释放的非 fastbin chunk(大于 fastbin 范围)
  • 特点
    • 不按大小排序,先进后出(FIFO)
    • 后续 malloc 时会根据大小转移到 smallbin 或 largebin
    • 所有非 fastbin 的 chunk 释放时都先进入 unsorted bin

unsorted bin 转化

  • malloc 请求触发归类
    当你 malloc 时,会优先在 unsorted bin 中查找是否有足够大的 chunk。
    如果 unsorted bin 中有 chunk 不足以分配请求的大小,这些 chunk 会被按大小归类,分别转移到 smallbin 或 largebin。
    chunk size ≤ smallbin 最大值,进入 smallbin
    chunk size > smallbin 最大值,进入 largebin
    这个过程中,所有遍历到但不满足分配条件的 chunk 都会被“搬家”到对应的 bin。

堆介绍

堆是动态分配内存的区域,通常用于存储在运行时需要动态分配的对象。堆内存的分配和释放由程序员控制,常见的操作包括 mallocfree 等。

操作系统内存布局示意图

前言

这类题目一般只会允许调用 Ptrace 系统调用

1
2
3
4
5
6
7
8
9
~/P/n/smallbox ❯❯❯ seccomp-tools dump ./smallbox                                                                                                                     (.venv)
[+] please input your shellcode:
ls
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x00 0x01 0x00000065  if (A != ptrace) goto 0003
 0002: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0003: 0x06 0x00 0x00 0x00000000  return KILL

ptrace 系统调用

ptrace 是 Linux 中的一个系统调用,可以让父进程控制子进程运行,并可以检查和改变子进程的内存和寄存器状态。它通常用于调试器和沙箱环境中。

前言

条件竞争通常发生在多个线程或进程同时访问共享资源时,导致程序行为不可预测。

例题

NepCtf 2025 - time

1
2
3
4
5
6
7
8
9
~/P/n/time ❯❯❯ pwn checksec ./time                                                                                                                                   (.venv)
[*] '/home/lhon901/Pwn/nepctf2025/time/time'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
 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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  pthread_t newthread[2]; // [rsp+0h] [rbp-10h] BYREF

  newthread[1] = __readfsqword(0x28u);
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  sub_2A31();
  while ( 1 )
  {
    while ( !(unsigned int)sub_2B0F() )
      ;
    pthread_create(newthread, 0, start_routine, 0);
  }
}


unsigned __int64 sub_2A31()
{
  char *argv[5]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v2; // [rsp+38h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("please input your name:");
  __isoc99_scanf("%100s", byte_50A0);
  puts("I will tell you all file names in the current directory!");
  argv[0] = "/bin/ls";
  argv[1] = "/";
  argv[2] = "-al";
  argv[3] = 0;
  if ( !fork() )
    execve("/bin/ls", argv, 0);
  wait(0);
  puts("good luck :-)");
  return v2 - __readfsqword(0x28u);
}


__int64 sub_2B0F()
{
  puts("input file name you want to read:");
  __isoc99_scanf("%s", file);
  if ( !strstr(file, "flag") )
    return 1;
  puts("flag is not allowed!");
  return 0;
}


unsigned __int64 __fastcall start_routine(void *a1)
{
  unsigned int v1; // eax
  int i; // [rsp+4h] [rbp-46Ch]
  int j; // [rsp+8h] [rbp-468h]
  int fd; // [rsp+Ch] [rbp-464h]
  _BYTE v6[96]; // [rsp+10h] [rbp-460h] BYREF
  _BYTE v7[16]; // [rsp+70h] [rbp-400h] BYREF
  _BYTE buf[1000]; // [rsp+80h] [rbp-3F0h] BYREF
  unsigned __int64 v9; // [rsp+468h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  sub_1329(v6);
  v1 = strlen(file);
  sub_1379(v6, file, v1);
  sub_14CB(v6, v7);
  puts("I will tell you last file name content in md5:");
  for ( i = 0; i <= 15; ++i )
    printf("%02X", (unsigned __int8)v7[i]);
  putchar(10);
  for ( j = 0; j <= 999; ++j )
    buf[j] = 0;
  fd = open(file, 0);
  if ( fd >= 0 )
  {
    read(fd, buf, 0x3E8u);
    close(fd);
    printf("hello ");
    printf(byte_50A0);
    puts(" ,your file read done!");
  }
  else
  {
    puts("file not found!");
  }
  return v9 - __readfsqword(0x28u);
}

程序只有这一个非栈上格式化字符串漏洞

Arm 汇编速探

函数调用约定

  • TODO: 写一片文章介绍 arm 汇编相关知识

ARM32(AArch32)调用约定

  • 前 4 个参数:r0 ~ r3

  • 返回值:r0

  • 多余参数:栈上传递,从右往左入栈

  • 堆栈平衡:被调用函数负责

  • 函数调用:bl/b,返回用 bx lrmov pc, lr