shellcode
shellcode
原理
- 就是一段可以插入到程序或系统中并被执行的代码,shellcode的作用就是getshell
编写
64位
1 | |
插入
1 | |
沙盒下的orw绕过
沙箱保护
- 对程序加入的一些保护,最常见的是禁用一些系统调用,比如exceve,使我们不可通过系统调用获取到权限,因此只能通过ROP的方式调用open,read,write等来读取并打印flag内同
ORW
就是open, read,write简写,就是打开,写入,输出flag
查看沙箱
利用seccomp-tools查看是否开启了沙箱,以及沙箱中一些允许的syscall
安装:
1 | |
检查:
1 | |
开启沙箱
prctl()函数调用
原型
1
2
3
4
5
6
7
8
9
10
11
12
13
14#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
// 主要关注prctl()函数的第一个参数,也就是option,设定的option的值的不同导致黑名单不同,介绍2个比较重要的option
// PR_SET_NO_NEW_PRIVS(38) 和 PR_SET_SECCOMP(22)
// option为38的情况
// 此时第二个参数设置为1,则 禁用execve系统调用 且子进程一样受用
prctl(38, 1LL, 0LL, 0LL, 0LL);
// option为22的情况
// 此时第二个参数为1,只允许调用read/write/_exit(not exit_group)/sigreturn这几个syscall
// 第二个参数为2,则为过滤模式,其中对syscall的限制通过参数3的结构体来自定义过滤规则。
prctl(22, 2LL, &v1);
seccomp()系统调用
原型
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
27
28
29
30
31
32
33
34
35__int64 sandbox()
{
__int64 v1; // [rsp+8h] [rbp-8h]
// 这里介绍两个重要的宏,SCMP_ACT_ALLOW(0x7fff0000U) SCMP_ACT_KILL( 0x00000000U)
// seccomp初始化,参数为0表示白名单模式,参数为0x7fff0000U则为黑名单模式
v1 = seccomp_init(0LL);
if ( !v1 )
{
puts("seccomp error");
exit(0);
}
// seccomp_rule_add添加规则
// v1对应上面初始化的返回值
// 0x7fff0000即对应宏SCMP_ACT_ALLOW
// 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit
// 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传0不做任何限制
seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL);
// seccomp_load->将当前seccomp过滤器加载到内核中
if ( seccomp_load(v1) < 0 )
{
// seccomp_release->释放seccomp过滤器状态
// 但对已经load的过滤规则不影响
seccomp_release(v1);
puts("seccomp error");
exit(0);
}
return seccomp_release(v1);
}
shellcode的写入
一般溢出的大小不够写入很长的ROP链,因此提供mmap()函数,从而给出一段栈上的内存
1 | |
1 | |

比如说
mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);
addr:(void *)0x123000- 这是请求映射的起始地址。
0x123000是一个具体的地址值。如果mmap调用成功,内核会尝试将内存映射到这个地址。如果地址不可用,内核会选择一个合适的地址。
- 这是请求映射的起始地址。
length:0x1000uLL- 这是请求映射的内存区域的长度,单位是字节。
0x1000是 16 进制表示,等于 4096 字节(1 页)。
- 这是请求映射的内存区域的长度,单位是字节。
prot:6- 这是内存区域的保护标志,定义了对该区域的访问权限。
6是PROT_READ | PROT_WRITE的组合,表示该区域可以读写。
- 这是内存区域的保护标志,定义了对该区域的访问权限。
flags:34- 这是映射的标志,定义了映射的类型和行为。
34是MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS的组合,具体含义如下:MAP_PRIVATE: 创建一个私有映射,对映射区域的修改不会反映到原始文件中。MAP_FIXED: 强制使用指定的地址addr,如果该地址不可用,调用会失败。MAP_ANONYMOUS: 创建一个匿名映射,不与任何文件关联。
- 这是映射的标志,定义了映射的类型和行为。
fd:-1- 这是文件描述符,用于指定映射的文件。
-1表示不映射任何文件,通常与MAP_ANONYMOUS一起使用。
- 这是文件描述符,用于指定映射的文件。
offset:0LL- 这是文件中的偏移量,用于指定映射的起始位置。
0LL表示从文件的开头开始映射。
- 这是文件中的偏移量,用于指定映射的起始位置。
orw绕过就是open flag,将flag写入某个区域,再write出来
题目
[极客大挑战 2019]Not Bad
checksec
懒得放截图了,反正64位什么都没开

只允许read,write,open系统调用
IDA

可以看到从0x123000给分配了0x1000字节空间,权限可写可执行
直接看第三个函数

存在栈溢出,但长度小
思路:
在mmap分配的区域进行orw,并且在这个区域写入flag并输出flag
1
2
3
4
5orw=asm(shellcraft.open('./flag'))
orw+=asm(shellcraft.read(3,mmap+0x100,0x100))
orw+=asm(shellcraft.write(1,mmap+0x100,0x100))
# 写在mmap+0x100的地方
# orw写在mmap来执行现在的问题是如何让rip指向那一块区域
发现程序有一个
jmp rsp可以利用那么我们可以构造栈

因为函数结束是有leave;retleave让rsp跑到了序号2(rbp)位置,rbp跑走了,然后ret再让rsp再移动到序号1的位置,此时与read相距0x28+8=0x30
所以是sub rsp,0x30
1 | |
完整:
1 | |
成功得到

一些 shellcode 模板
x86-64/x86
32位
1 | |
64
1 | |
arm
32
1 | |